Explorar o código

Merge remote-tracking branch 'origin/master'

gogs hai 1 mes
pai
achega
673a0eb917

+ 0 - 3
app/Http/Controllers/Api/BaseController.php

@@ -3,10 +3,7 @@
 namespace App\Http\Controllers\Api;
 
 use App\Http\Controllers\Controller;
-use App\Models\SystemL;
-use App\Service\DisPatchAppService;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Storage;
 
 class BaseController extends Controller

+ 25 - 0
app/Http/Controllers/Api/EmployeeController.php

@@ -230,6 +230,31 @@ class EmployeeController extends BaseController
         }else{
             return $this->json_return(201,$data);
         }
+    }
+
+    public function companySet(Request $request)
+    {
+        $service = new EmployeeService();
+        $user = $request->userData;
+        list($status,$data) = $service->companySet($request->all(),$user);
 
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function companyDetail(Request $request)
+    {
+        $service = new EmployeeService();
+        $user = $request->userData;
+        list($status,$data) = $service->companyDetail($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
     }
 }

+ 13 - 0
app/Http/Controllers/Api/PriorityController.php

@@ -59,4 +59,17 @@ class PriorityController extends BaseController
             return $this->json_return(201,$data);
         }
     }
+
+    public function priorityDetail(Request $request)
+    {
+        $service = new PriorityService();
+        $user = $request->userData;
+        list($status,$data) = $service->priorityDetail($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
 }

+ 76 - 0
app/Http/Controllers/Api/TeamController.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Service\TeamService;
+use Illuminate\Http\Request;
+
+class TeamController extends BaseController
+{
+    public function teamEdit(Request $request)
+    {
+        $service = new TeamService();
+        $user = $request->userData;
+        list($status,$data) = $service->teamEdit($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function teamAdd(Request $request)
+    {
+        $service = new TeamService();
+        $user = $request->userData;
+        list($status,$data) = $service->teamAdd($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+
+    }
+
+    public function teamDel(Request $request)
+    {
+        $service = new TeamService();
+        $user = $request->userData;
+        list($status,$data) = $service->teamDel($request->all());
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+
+    }
+
+    public function teamList(Request $request)
+    {
+        $service = new TeamService();
+        $user = $request->userData;
+        list($status,$data) = $service->teamList($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function teamDetail(Request $request)
+    {
+        $service = new TeamService();
+        $user = $request->userData;
+        list($status,$data) = $service->teamDetail($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+}

+ 3 - 3
app/Jobs/ProcessOssTask.php

@@ -16,7 +16,7 @@ class ProcessOssTask implements ShouldQueue
 {
     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
-    public $timeout = 300;
+    public $timeout = 50;
 
     public function __construct()
     {
@@ -36,12 +36,12 @@ class ProcessOssTask implements ShouldQueue
 
                     foreach ($tasks as $task) {
                         $status_db = 2;
-                        if($task['type'] == 1){
+                        if($task['type'] == SysOssTasks::type_one){
                             //删除
                             list($status, $msg) = $service->createOssUploadOldSingle($task->url);
                             if($status) $status_db = 1;
 
-                        }elseif ($task['type'] == 2){
+                        }elseif ($task['type'] == SysOssTasks::type_two){
                             //新增
                             list($status, $msg) = $service->createOssUploadSingle($task->url);
                             if($status) $status_db = 1;

+ 13 - 0
app/Model/Company.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Model;
+
+class Company extends DataScopeBaseModel
+{
+    protected $guarded = [];
+    protected $table = "company"; //指定表
+    const CREATED_AT = null;
+    const UPDATED_AT = null;
+    protected $dateFormat = 'U';
+    public static $field = ['*'];
+}

+ 7 - 5
app/Model/Item.php

@@ -12,13 +12,17 @@ class Item extends DataScopeBaseModel
     protected $dateFormat = 'U';
     const employee_column = "crt_id";
 
-    public static $field = ['title','id','code','start_time','end_time','mark','crt_id','crt_time','state','budget','charge_id','item_attribute','field'];
+    public static $field = ['title','id','code','start_time','end_time','mark','crt_id','crt_time','state','budget','charge_id','item_attribute','field','is_review_required','review_id','priority_id'];
     public static $report_field_1 = ['title','id','code','start_time','end_time','mark','budget','field'];
 
+    const TYPE_MINUS_TWO = -2;
+    const TYPE_MINUS_ONE = -1;
     const TYPE_ONE = 1;
     const TYPE_TWO = 2;
     const TYPE_THREE = 3;
     const State_Type = [
+        self::TYPE_MINUS_ONE => '审核中',
+        self::TYPE_MINUS_TWO => '已超期',
         self::TYPE_ONE => '待开始',
         self::TYPE_TWO => '进行中',
         self::TYPE_THREE => '已完成',
@@ -38,15 +42,13 @@ class Item extends DataScopeBaseModel
     // 作用域 里面关联了客户自定义的表头数据
     public function scopeWithCustomData($query, $menuId, $user)
     {
-        if (empty($menuId)) {
-            return $query;
-        }
+        if (empty($menuId)) return $query;
 
         $top_depart_id = $user['top_depart_id'];
 
         return $query->with(['customFieldValues' => function ($subQuery) use ($menuId, $top_depart_id) {
             //只查询设置的id 和 自定义字段的值 还有业务单据/档案的id(这个必须要 预加载里没有主外键关联关系就查询失败)
-            $subQuery->select(['definition_id', 'field_value', 'model_id'])
+            $subQuery->select(['definition_id', 'field_value', 'model_id', 'field_type'])
                 ->where('top_depart_id', $top_depart_id)
                 ->whereHas('definition', function ($q) use ($menuId) {
                     $q->where('menu_id', $menuId);

+ 1 - 1
app/Model/Priority.php

@@ -12,7 +12,7 @@ class Priority extends DataScopeBaseModel
     const UPDATED_AT = 'upd_time';
     protected $dateFormat = 'U';
 
-    public static $field = ['title','id','code','sort','type'];
+    public static $field = ['title','id','code','sort','type', 'is_use'];
 
     const TYPE_ONE = 1;
     const TYPE_TWO = 2;

+ 4 - 1
app/Model/SysOssTasks.php

@@ -7,9 +7,12 @@ use Illuminate\Database\Eloquent\Model;
 class SysOssTasks extends Model
 {
     protected $guarded = [];
-    protected $table = "sys_oss_tasks"; //指定表
+    protected $table = "sys_oss_tasks";
     const CREATED_AT = 'crt_time';
     const UPDATED_AT = 'upd_time';
     protected $dateFormat = 'U';
 
+    const type_one = 1;
+    const type_two = 2;
+    const job = 'sync_oss_file';
 }

+ 21 - 0
app/Model/Team.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Model;
+
+class Team extends DataScopeBaseModel
+{
+    protected $table = "team"; //指定表
+    const CREATED_AT = 'crt_time';
+    const UPDATED_AT = 'upd_time';
+    protected $dateFormat = 'U';
+    const employee_column = "crt_id";
+
+    public static $field = ['title','id','code','mark','crt_id','crt_time','state','charge_id'];
+
+    const TYPE_ZERO = 0;
+    const TYPE_ONE = 1;
+    const State_Type = [
+        self::TYPE_ZERO => '停用',
+        self::TYPE_ONE => '启用',
+    ];
+}

+ 12 - 0
app/Model/TeamDetails.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Model;
+
+class TeamDetails extends DataScopeBaseModel
+{
+    protected $guarded = [];
+    protected $table = "team_details"; //指定表
+    const CREATED_AT = 'crt_time';
+    const UPDATED_AT = 'upd_time';
+    protected $dateFormat = 'U';
+}

+ 79 - 59
app/Service/CustomFieldSettingService.php

@@ -2,6 +2,7 @@
 
 namespace App\Service;
 
+use App\Jobs\ProcessOssTask;
 use App\Model\CustomFieldDefinitions;
 use App\Model\SysMenu;
 use App\Model\SysModules;
@@ -107,11 +108,11 @@ class CustomFieldSettingService extends Service
         $title = SysMenu::where('id', $data['menu_id'])->value('title');
         if(empty($title)) return [false, '菜单不存在或已被删除'];
 
-        $bool = SysModules::where('del_time',0)
-            ->where('top_depart_id', $data['top_depart_id'])
-            ->where('menu_id', $data['menu_id'])
-            ->exists();
-        if(! $bool) return [false, $title . '不允许增加自定义表头'];
+//        $bool = SysModules::where('del_time',0)
+//            ->where('top_depart_id', $data['top_depart_id'])
+//            ->where('menu_id', $data['menu_id'])
+//            ->exists();
+//        if(! $bool) return [false, $title . '不允许增加自定义表头'];
 
         if(empty($data['data'])) return [false,'自定义数据不能为空'];
 
@@ -163,71 +164,90 @@ class CustomFieldSettingService extends Service
     public static function syncCustomFieldData($id, $data, $user)
     {
         $top_depart_id = $user['top_depart_id'];
-        if (empty($data['menu_id'])) return [false, '菜单ID不能为空'];
-        $menuId = $data['menu_id'];
-
-        $definitions = CustomFieldDefinitions::where('menu_id', $menuId)
+        if (empty($data['menu_id'])) return [true, ''];
+        $definitions = CustomFieldDefinitions::where('menu_id', $data['menu_id'])
             ->where('top_depart_id', $top_depart_id)
             ->get();
+        if(empty($definitions)) return [true, ''];
 
         $pendingTasks = [];
-
         $time = time();
-        foreach ($definitions as $df) {
-            $key = $df->field_key;
-            // 如果请求数据中没有这个自定义字段的 key,跳过(支持局部更新)
-            if (!array_key_exists($key, $data)) continue;
-
-            $newVal = (string)($data[$key] ?? '');
-
-            // --- 附件类型逻辑处理 ---
-            if ($df->field_type === 'file') {
-                // 查询数据库中已有的旧路径
-                $oldVal = DB::table('custom_field_values')
-                        ->where('model_id', $id)
-                        ->where('definition_id', $df->id)
-                        ->value('field_value') ?? '';
-
-                if ($newVal !== $oldVal) {
-                    //产生一个【删除任务】
-                    if (!empty($oldVal)) {
-                        $pendingTasks[] = [
-                            'type'          => 1, // 删除旧文件
-                            'url'           => $oldVal,
-                            'top_depart_id' => $top_depart_id,
-                            'crt_time'      => $time,
-                        ];
-                    }
-
-                    //产生一个【新增同步任务】
-                    if (!empty($newVal)) {
-                        $pendingTasks[] = [
-                            'type'          => 2, // 新增同步到 OSS
-                            'url'           => $newVal,
-                            'top_depart_id' => $top_depart_id,
-                            'crt_time'      => $time,
-                        ];
+        try {
+            foreach ($definitions as $df) {
+                $key = $df->field_key;
+                // 如果请求数据中没有这个自定义字段的 key,跳过
+                if (!array_key_exists($key, $data)) continue;
+
+                $newVal = (string)($data[$key] ?? '');
+
+                // --- 附件类型逻辑处理 ---
+                if ($df->field_type === CustomFieldDefinitions::TYPE_TWO) {
+                    // 查询数据库中已有的旧路径
+                    $oldVal = DB::table('custom_field_values')
+                            ->where('model_id', $id)
+                            ->where('definition_id', $df->id)
+                            ->value('field_value') ?? '';
+
+                    if ($newVal !== $oldVal) {
+                        //产生一个【删除任务】
+                        if (!empty($oldVal)) {
+                            $pendingTasks[] = [
+                                'type'          => SysOssTasks::type_one, // 删除旧文件
+                                'url'           => $oldVal,
+                                'top_depart_id' => $top_depart_id,
+                                'crt_time'      => $time,
+                            ];
+                        }
+
+                        //产生一个【新增同步任务】
+                        if (!empty($newVal)) {
+                            $pendingTasks[] = [
+                                'type'          => SysOssTasks::type_two, // 新增同步到 OSS
+                                'url'           => $newVal,
+                                'top_depart_id' => $top_depart_id,
+                                'crt_time'      => $time,
+                            ];
+                        }
                     }
                 }
+
+                // --- 统一更新业务值表 (文本或附件路径) ---
+                DB::table('custom_field_values')->updateOrInsert(
+                    [
+                        'model_id'      => $id,
+                        'definition_id' => $df->id,
+                        'top_depart_id' => $top_depart_id,
+                        'field_type' => $df->field_type
+                    ],
+                    [
+                        'field_value' => $newVal, // 如果是删除,这里存入空字符串
+                        'upd_time'    => time()
+                    ]
+                );
             }
 
-            // --- 统一更新业务值表 (文本或附件路径) ---
-            DB::table('custom_field_values')->updateOrInsert(
-                [
-                    'model_id'      => $id,
-                    'definition_id' => $df->id,
-                    'top_depart_id' => $top_depart_id,
-                ],
-                [
-                    'field_value' => $newVal, // 如果是删除,这里存入空字符串
-                    'upd_time'    => time()
-                ]
-            );
-        }
+            //需要执行的oss的任务 调用这个方法的地方在 事务结束后触发下队列
+            if(! empty($pendingTasks)) {
+                SysOssTasks::insert($pendingTasks);
 
-        //需要执行的oss的任务 调用这个方法的地方在 事务结束后触发下队列 todo
-        if(! empty($pendingTasks)) SysOssTasks::insert($pendingTasks);
+                ProcessOssTask::dispatch()->onQueue(SysOssTasks::job);
+            }
+        }catch (\Throwable $exception){
+            return [false, $exception->getMessage()];
+        }
 
         return [true, ''];
     }
+
+    public function fillDataCustomField(&$data){
+        if(! empty($data['custom_field_values'])){
+            $fileUploadService = new FileUploadService();
+            foreach ($data['custom_field_values'] as $key => $value){
+                if($value['field_type'] == CustomFieldDefinitions::TYPE_TWO){
+                    $img_str = $fileUploadService->getFileShow($value['field_value']);
+                    $data['custom_field_values'][$key]['field_value_show'] = $img_str;
+                }
+            }
+        }
+    }
 }

+ 1 - 1
app/Service/DeviceWorkService.php

@@ -708,7 +708,7 @@ class DeviceWorkService extends Service
 
         $emp = (new EmployeeService())->getEmployeeMap(array_unique(array_column($data['data'],'crt_id')));
         $item = (new ItemService())->getItemMap(array_unique(array_column($data['data'],'item_id')));
-        $map = ArchiveService::fillIsArchive(array_unique(array_column($data['data'],'month')), $user);
+        $map = ArchiveService::fillIsArchive(array_unique(array_column($data['data'],'order_time')), $user);
         foreach ($data['data'] as $key => $value){
             $data['data'][$key]['crt_time'] = $value['crt_time'] ? date('Y-m-d H:i:s',$value['crt_time']) : '';
             $data['data'][$key]['order_time'] = $value['order_time'] ? date('Y-m-d',$value['order_time']) : '';

+ 60 - 1
app/Service/EmployeeService.php

@@ -3,6 +3,7 @@
 namespace App\Service;
 
 use App\Model\CalendarDetails;
+use App\Model\Company;
 use App\Model\Depart;
 use App\Model\Employee;
 use App\Model\EmployeeDepartPermission;
@@ -12,13 +13,71 @@ use App\Model\EmployeeWorkRange;
 use App\Model\Role;
 use App\Model\RoleMenu;
 use App\Model\RoleMenuButton;
-use App\Model\SysMenu;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Hash;
 use Mockery\Exception;
 
 class EmployeeService extends Service
 {
+    public function companySet($data, $user)
+    {
+        // 1. 基础校验
+        if (empty($data['title'])) return [false, '公司全称不能为空'];
+        if (empty($data['code'])) return [false, '统一社会信用代码不能为空'];
+
+        // 2. 准备查询条件
+        $attributes = [
+            'top_depart_id' => $user['top_depart_id']
+        ];
+
+        // 3. 准备需要更新或插入的数据
+        $values = [
+            'title'                => $data['title'],
+            'code'                 => $data['code'],
+            'address'              => $data['address'] ?? '',
+            'legal_representative' => $data['legal_representative'] ?? '',
+            'mobile'               => $data['mobile'] ?? '',
+            'e_mail'               => $data['e_mail'] ?? '',
+            'industry'             => $data['industry'] ?? '',
+            'start_time'           => $data['start_time'] ?? '',
+            'money'                => $data['money'] ?? '',
+            'number'               => $data['number'] ?? '',
+            'del_time'             => 0, // 建议重置删除标志,防止在更新已软删除的数据时出错
+        ];
+
+        try {
+            Company::updateOrCreate($attributes, $values);
+        } catch (\Exception $e) {
+            return [false, '保存失败:' . $e->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    public function companyDetail($data, $user)
+    {
+        $company = Company::where('del_time',0)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->first();
+        if(empty($company)) return [true, []];
+
+        return [true, $company->toArray()];
+    }
+
+    public static function getCompanyDetail($user){
+        $company = Company::where('del_time',0)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->first();
+        if(empty($company)){
+            //默认名字
+            $title = Depart::where('id', $user['top_depart_id'])->value('title');
+            $return['title'] = $title;
+            return $return;
+        }
+
+        return $company->toArray();
+    }
+
     public function employeeEditOther($data,$user){
         list($status,$msg) = $this->employeeOtherRule($data,$user);
         if(!$status) return [$status,$msg];

+ 13 - 4
app/Service/ExportFileService.php

@@ -483,9 +483,12 @@ class ExportFileService extends Service
         $totalRow[] = $grandTotalSalary;
         $exportData[] = $totalRow;
 
+        //获取公司基本信息
+        $company = EmployeeService::getCompanyDetail($user);
+
         $file_name = "项目工资统计表_" . date("Y-m-d") . "_". rand(1000,9999);
         $filename =  $file_name . '.' . 'xlsx';
-        $bool = Excel::store(new ItemSalarySheetExport($projects, $exportData, Depart::where('id', $user['top_depart_id'])->value('title')),"/public/export/{$filename}", null, 'Xlsx', []);
+        $bool = Excel::store(new ItemSalarySheetExport($projects, $exportData, $company['title'] ?? ''),"/public/export/{$filename}", null, 'Xlsx', []);
 
         return [true, $filename];
     }
@@ -697,11 +700,14 @@ class ExportFileService extends Service
             ];
         }
 
+        //获取公司基本信息
+        $company = EmployeeService::getCompanyDetail($user);
+
         $file_name = "项目工资分摊统计表_" . date("Y-m-d") . "_". rand(1000,9999);
         $filename =  $file_name . '.xlsx';
 
         Excel::store(
-            new ItemSalaryFTMultipleSheetExport($monthsData, Depart::where('id', $user['top_depart_id'])->value('title')),
+            new ItemSalaryFTMultipleSheetExport($monthsData, $company['title'] ?? ''),
             "/public/export/{$filename}"
         );
 
@@ -851,11 +857,14 @@ class ExportFileService extends Service
             ->setTimezone(config('app.timezone')) // 转换为 Laravel 配置的时区
             ->format('Y');
 
+        //获取公司基本信息
+        $company = EmployeeService::getCompanyDetail($user);
+
         // 3. 组织多 Sheet 格式数据
         $monthsData = [
             $year => [
-                'tax_id'          => $user->tax_id ?? '***********',
-                'company_name'    => Depart::where('id', $user['top_depart_id'])->value('title'),
+                'tax_id'          => $company['code'] ?? '',
+                'company_name'    => $company['title'] ?? '',
                 'items'           => $items,
                 'dynamic_headers' => $dynamicHeaders
             ]

+ 241 - 0
app/Service/ImportService.php

@@ -21,6 +21,8 @@ use App\Model\PLeaveOverOrder;
 use App\Model\Priority;
 use App\Model\RuleSet;
 use App\Model\RuleSetDetails;
+use App\Model\Team;
+use App\Model\TeamDetails;
 use Illuminate\Support\Facades\DB;
 use Maatwebsite\Excel\Facades\Excel;
 use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\F;
@@ -47,6 +49,7 @@ class ImportService extends Service
         'feeOrder', // 费用项目单
         'RDOrder', // 研发支出辅助帐
         'priority', // 优先级
+        'team', // 团队
     ];
 
     public function getTableTitleXls($data,$user){
@@ -4105,6 +4108,244 @@ class ImportService extends Service
         return [$error_string, $update];
     }
 
+    // 团队 -----------------------------------
+    private function getTeamList($array, $user, $index){
+        $codes = array_unique(array_filter(array_column($array,$index)));
+
+        return Team::where('del_time', 0)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('code', $codes)
+            ->pluck('id','code')
+            ->toArray();
+    }
+
+    public function teamImport($array, $user, $other_param)
+    {
+        $upload = $array[0];
+        list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
+        if (!$status) return [false, $msg];
+        $table_config = $msg;
+
+        unset($array[0]);
+        if (empty($array)) return [false, '导入数据不能为空'];
+
+        // 1. 公共校验 (必填、唯一性等)
+        list($array, $error) = $this->checkCommon($array, $table_config);
+        if (!empty($error)) return [0, $error];
+
+        // 2. 业务详细校验 (包含一致性校验、明细解析)
+        list($error, $update_map, $detail_data_map) = $this->teamCheck($array, $user, $table_config);
+        if (!empty($error)) return [0, $error];
+
+        $time = time();
+        $insert_data = [];
+        $update_data = [];
+        $all_detail_insert = [];
+        $update_main_ids = [];
+
+        $keys = array_column($table_config, 'key');
+        $codeIdx = array_search('code', $keys);
+
+        // 3. 数据分拣(聚合平铺数据)
+        foreach ($array as $key => $value) {
+            $itemCode = trim($value[$codeIdx]);
+            $main_tmp = [];
+            foreach ($value as $k => $val) {
+                if (!empty($table_config[$k]['is_main'])) {
+
+                    if ($table_config[$k]['key'] == 'employee_title') continue;
+
+                    $main_tmp[$table_config[$k]['key']] = $val;
+                }
+            }
+
+            if (isset($update_map[$key])) {
+                // 更新逻辑:以 ID 为键去重
+                $itemId = $update_map[$key];
+                $update_main_ids[$itemId] = $itemId;
+                $update_data[$itemId] = array_merge($main_tmp, ['id' => $itemId]);
+
+                if (isset($detail_data_map[$key])) {
+                    foreach ($detail_data_map[$key] as $d) {
+                        $all_detail_insert[] = array_merge($d, [
+                            'team_id' => $itemId,
+                            'crt_time' => $time,
+                            'top_depart_id' => $user['top_depart_id']
+                        ]);
+                    }
+                }
+            } else {
+                // 新增逻辑:以项目编码为键去重
+                if (!isset($insert_data[$itemCode])) {
+                    $main_tmp['top_depart_id'] = $user['top_depart_id'];
+                    $main_tmp['crt_id'] = $user['id'];
+                    $main_tmp['crt_time'] = $time;
+                    $insert_data[$itemCode] = $main_tmp;
+                }
+
+                if (isset($detail_data_map[$key])) {
+                    foreach ($detail_data_map[$key] as $d) {
+                        $all_detail_insert[] = array_merge($d, [
+                            '_code' => $itemCode,
+                            'crt_time' => $time,
+                            'top_depart_id' => $user['top_depart_id']
+                        ]);
+                    }
+                }
+            }
+        }
+
+        DB::beginTransaction();
+        try {
+            // 4. 执行新增主表
+            $new_item_maps = [];
+            if (!empty($insert_data)) {
+                foreach (array_chunk(array_values($insert_data), 500) as $chunk) {
+                    Team::insert($chunk);
+                }
+                $new_item_maps = Team::whereIn('code', array_keys($insert_data))
+                    ->where('del_time', 0)
+                    ->where('top_depart_id', $user['top_depart_id'])
+                    ->pluck('id', 'code')->toArray();
+            }
+
+            // 5. 执行更新主表
+            if (!empty($update_data)) {
+                foreach ($update_data as $id => $uItem) {
+                    unset($uItem['id']);
+                    Team::where('id', $id)->update($uItem);
+                }
+            }
+
+            // 6. 处理明细表 (先全删后插)
+            if (!empty($update_main_ids)) {
+                TeamDetails::whereIn('team_id', array_values($update_main_ids))
+                    ->where('del_time', 0)
+                    ->update(['del_time' => $time]);
+            }
+
+            foreach ($all_detail_insert as &$di) {
+                if (isset($di['_code'])) {
+                    $di['item_id'] = $new_item_maps[$di['_code']] ?? 0;
+                    unset($di['_code']);
+                }
+            }
+            unset($di);
+
+            if (!empty($all_detail_insert)) {
+                foreach (array_chunk($all_detail_insert, 500) as $chunk) {
+                    TeamDetails::insert($chunk);
+                }
+            }
+
+            DB::commit();
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
+        }
+
+        return [true, ''];
+    }
+
+    private function teamCheck(&$array, $user, $table_config)
+    {
+        $keys = array_column($table_config, 'key');
+        $codeIdx = array_search('code', $keys);
+        $stateIdx = array_search('state', $keys);
+        $code2Idx = array_search('code_2', $keys);  // 工号列
+        $empIdx = array_search('charge_id', $keys);
+
+        // 假设 $array 是你的数据集
+        $allEmpNumbers = array_values(array_filter(array_unique([
+            array_column($array, $empIdx),
+            array_column($array, $code2Idx)
+        ])));
+
+        $dbEmps = Employee::where('del_time', 0)
+            ->whereIn('number', $allEmpNumbers)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->pluck('id', 'number')->toArray();
+
+        $code_map = $this->getTeamList($array, $user, $codeIdx);
+        $man_map = $this->getFlatDataList2($array, $user, $code2Idx);
+
+        $errors = [];
+        $update_mapping = [];
+        $detail_storage = [];
+        $mainDataConsistency = []; // 一致性校验
+
+        $state_type_map = array_flip(Team::State_Type);
+
+        // 识别主表字段列索引
+        $mainColIndices = [];
+        foreach ($table_config as $index => $conf) {
+            if (!empty($conf['is_main'])) $mainColIndices[$index] = $conf['value'];
+        }
+
+        foreach ($array as $rowIndex => $rowValue) {
+            $displayLine = $rowIndex + 1;
+            $valCode = trim($rowValue[$codeIdx] ?? '');
+            $valEmp = trim($rowValue[$empIdx] ?? '');
+            if ($valCode === '') continue;
+
+            // --- A. 主表一致性校验 ---
+            if (!isset($mainDataConsistency[$valCode])) {
+                $mainDataConsistency[$valCode] = array_intersect_key($rowValue, $mainColIndices);
+            } else {
+                foreach ($mainColIndices as $idx => $label) {
+                    if (trim($rowValue[$idx] ?? '') != trim($mainDataConsistency[$valCode][$idx] ?? '')) {
+                        $errors[] = "第{$displayLine}行:团队[{$valCode}]的主表信息[{$label}]与前文不一致";
+                    }
+                }
+            }
+
+            // --- B. 基础校验 ---
+            if (isset($code_map[$valCode])) {
+                $update_mapping[$rowIndex] = $code_map[$valCode];
+            }
+
+            // 状态
+            $state_text = $rowValue[$stateIdx] ?? '';
+            if (!isset($state_type_map[$state_text])) {
+                $errors[] = "第{$displayLine}行:状态[{$state_text}]无效";
+            } else {
+                $array[$rowIndex][$stateIdx] = $state_type_map[$state_text];
+            }
+
+            // D. 人员校验
+            if(! empty($valEmp)){
+                if (!isset($dbEmps[$valEmp])) {
+                    $errors[] = "第{$displayLine}行:负责人工号[{$valEmp}]不存在";
+                } else {
+                    $array[$rowIndex][$empIdx] = $dbEmps[$valEmp];
+                }
+            }
+
+            $subCode  = trim($rowValue[$code2Idx] ?? '');
+
+            if ($subCode !== '') {
+                if (!isset($man_map[$subCode])) {
+                    $errors[] = "第{$displayLine}行:人员工号[{$subCode}]不存在";
+                } else {
+                    $detail_storage[$rowIndex][] = ['data_id' => $man_map[$subCode]];
+                }
+            }
+        }
+
+        $error_str = !empty($errors) ? implode('|', $errors) : "";
+        return [$error_str, $update_mapping, $detail_storage];
+    }
+
+    private function getFlatDataList2($array, $user, $code2Idx)
+    {
+        $codes = array_unique(array_filter(array_column($array, $code2Idx)));
+        return Employee::where('del_time', 0)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('number', $codes)
+            ->pluck('id', 'number')
+            ->toArray();
+    }
+
     /**
      * 解析并校验时间
      */

+ 31 - 15
app/Service/ItemService.php

@@ -6,6 +6,8 @@ use App\Model\Device;
 use App\Model\Employee;
 use App\Model\Item;
 use App\Model\ItemDetails;
+use App\Model\Priority;
+use App\Model\SysMenu;
 use Illuminate\Support\Facades\DB;
 
 class ItemService extends Service
@@ -28,6 +30,14 @@ class ItemService extends Service
             $model->charge_id = $data['charge_id'] ?? 0;
             $model->field = $data['field'] ?? "";
             $model->item_attribute = $data['item_attribute'] ?? 0;
+
+            //项目管理的字段
+            if($user['select_tree_type'] == SysMenu::tree_type_one){
+                $model->is_review_required = $data['is_review_required'] ?? 0;
+                $model->review_id = $data['review_id'] ?? 0;
+                $model->priority_id = $data['priority_id'] ?? 0;
+            }
+
             $model->save();
 
             $time = time();
@@ -36,6 +46,12 @@ class ItemService extends Service
                 ->update(['del_time' => $time]);
             $this->saveDetail($model->id, $time, $data);
 
+            list($status, $msg) = CustomFieldSettingService::syncCustomFieldData($model->id,$data,$user);
+            if (! $status) {
+                DB::rollBack();
+                return [false, $msg];
+            }
+
             DB::commit();
         }catch (\Exception $exception){
             DB::rollBack();
@@ -63,17 +79,20 @@ class ItemService extends Service
             $model->charge_id = $data['charge_id'] ?? 0;
             $model->field = $data['field'] ?? "";
             $model->item_attribute = $data['item_attribute'] ?? 0;
+            $model->is_review_required = $data['is_review_required'] ?? 0;
+            $model->review_id = $data['review_id'] ?? 0;
+            $model->priority_id = $data['priority_id'] ?? 0;
             $model->crt_id = $user['id'];
             $model->top_depart_id = $data['top_depart_id'];
             $model->save();
 
             $this->saveDetail($model->id, time(), $data);
 
-//            list($status, $msg) = CustomFieldSettingService::syncCustomFieldData($model->id,$data,$user);
-//            if (!$status) {
-//                DB::rollBack();
-//                return [false, $msg];
-//            }
+            list($status, $msg) = CustomFieldSettingService::syncCustomFieldData($model->id,$data,$user);
+            if (!$status) {
+                DB::rollBack();
+                return [false, $msg];
+            }
 
             DB::commit();
         }catch (\Exception $exception){
@@ -153,18 +172,10 @@ class ItemService extends Service
             }
         }
 
-        $detail = [
+        return [
             'man_list' => $unit,
             'device_list' => $receipt,
         ];
-
-        //foreach ($detail as $key => $value) {
-           // if (empty($value)) {
-                //$detail[$key] = (object)[]; // 转成 stdClass 对象
-            //}
-        //}
-
-        return $detail;
     }
 
     public function getItemMap($ids){
@@ -210,12 +221,17 @@ class ItemService extends Service
             ->first();
         if(empty($customer)) return [false,'项目不存在或已被删除'];
         $customer = $customer->toArray();
+
+        //填充自定义表头附件地址
+        (new CustomFieldSettingService())->fillDataCustomField($customer);
+
         $customer['start_time']  = ! empty($customer['start_time']) ? date("Y-m-d", $customer['start_time']) : "";
         $customer['end_time']  = ! empty($customer['end_time']) ? date("Y-m-d", $customer['end_time']) : "";
         $customer['crt_name'] = Employee::where('id',$customer['crt_id'])->value('title');
         $customer['charge_name'] = Employee::where('id',$customer['charge_id'])->value('title');
         $customer['crt_time'] = $customer['crt_time'] ? date("Y-m-d H:i:s",$customer['crt_time']): '';
-        $customer['state_title'] = Employee::State_Type[$customer['state']] ?? '';
+        $customer['state_title'] = Item::State_Type[$customer['state']] ?? '';
+        $customer['priority_title'] = Priority::where('id', $customer['priority_id'])->value('title') ?? "";
 
         $details = $this->getDetail($data['id']);
         $customer = array_merge($customer, $details);

+ 2 - 2
app/Service/MiddleGroundService.php

@@ -309,7 +309,7 @@ class MiddleGroundService extends Service
             //权限
             $sysList = SysMenu::where('del_time',0)
                 ->where('type', SysMenu::type_zero)
-                ->select('title','icon','uri','parent_id','sort','id','is_authority', 'state')
+                ->select('title','icon','uri','parent_id','sort','id', 'state')
                 ->orderBy('sort','desc')
                 ->get()
                 ->toArray();
@@ -321,7 +321,7 @@ class MiddleGroundService extends Service
         }elseif($type == "all"){
             $sysList = SysMenu::where('del_time',0)
                 ->where('type', SysMenu::type_one)
-                ->select('title','icon','uri','parent_id','sort','id','is_authority', 'state')
+                ->select('title','icon','uri','parent_id','sort','id', 'state')
                 ->orderBy('sort','desc')
                 ->get()
                 ->toArray();

+ 8 - 2
app/Service/PersonSalaryService.php

@@ -158,6 +158,8 @@ class PersonSalaryService extends Service
         $customer = $customer->toArray();
         $customer['crt_name'] = Employee::where('id',$customer['crt_id'])->value('title');
         $customer['crt_time'] = $customer['crt_time'] ? date("Y-m-d H:i:s",$customer['crt_time']): '';
+        $map = ArchiveService::fillIsArchive($customer['month'], $user);
+        $customer['is_archive'] = $map[$customer['month']] ?? false;
         $customer['month'] = $customer['month'] ? date("Y-m",$customer['month']): '';
 
         $details = $this->getDetail($data['id']);
@@ -193,7 +195,7 @@ class PersonSalaryService extends Service
     public function MonthlyPsOrderList($data,$user){
         $model = $this->MonthlyPsOrderCommon($data, $user);
         $list = $this->limit($model,'',$data);
-        $list = $this->fillData($list);
+        $list = $this->fillData($list, $user);
 
         return [true, $list];
     }
@@ -246,14 +248,18 @@ class PersonSalaryService extends Service
         return [true, ''];
     }
 
-    public function fillData($data){
+    public function fillData($data, $user){
         if(empty($data['data'])) return $data;
 
         $emp = (new EmployeeService())->getEmployeeMap(array_unique(array_column($data['data'],'crt_id')));
+        $map = ArchiveService::fillIsArchive(array_unique(array_column($data['data'],'month')), $user);
+
         foreach ($data['data'] as $key => $value){
             $data['data'][$key]['crt_time'] = $value['crt_time'] ? date('Y-m-d H:i:s',$value['crt_time']) : '';
+            $data['data'][$key]['is_archive'] = $map[$value['month']] ?? false;
             $data['data'][$key]['month'] = $value['month'] ? date('Y-m',$value['month']) : '';
             $data['data'][$key]['crt_name'] = $emp[$value['crt_id']] ?? '';
+
         }
 
         return $data;

+ 16 - 7
app/Service/PriorityService.php

@@ -40,6 +40,7 @@ class PriorityService extends Service
                 $model->code = $value['code'];
                 $model->type = $value['type'];
                 $model->sort = $value['sort'];
+                $model->is_use = $value['is_use'];
                 $model->top_depart_id = $value['top_depart_id'];
                 $model->save();
             }
@@ -73,26 +74,34 @@ class PriorityService extends Service
 
         if(! empty($data['title'])) $model->where('title', 'LIKE', '%'.$data['title'].'%');
         if(! empty($data['code'])) $model->where('code', 'LIKE', '%'.$data['code'].'%');
+        if(isset($data['is_use'])) $model->where('is_use', $data['is_use']);
+        if(! empty($data['id'])) $model->whereIn('id', $data['id']);
 
         return $model;
     }
 
     public function priorityList($data, $user){
         $model = $this->priorityCommon($data, $user);
-
-        $list = $model->get()->toArray();
+        $list = $this->limit($model,'',$data);
         $list = $this->fillPriorityList($list, $user);
 
         return [true, $list];
     }
 
-    public function fillPriorityList($list, $user, $is_export = false){
+    public function priorityDetail($data, $user){
+        if(empty($data['id'])) return [false,'id不能为空'];
+        list($status, $return) = $this->priorityList(['id' => [$data['id']]], $user);
+        $return = $return['data'][0] ?? [];
+
+        return [true, $return];
+    }
+
+    public function fillPriorityList($list, $user){
         if(empty($list)) return $list;
 
-        if($is_export){
-            foreach ($list['data'] as $key => $value){
-                $list['data'][$key]['type_title'] = Priority::TYPE_TITLE[$value['type']] ?? "";
-            }
+        foreach ($list['data'] as $key => $value){
+            $list['data'][$key]['type_title'] = Priority::TYPE_TITLE[$value['type']] ?? "";
+            $list['data'][$key]['is_use_title'] = Priority::IS_USE[$value['is_use']] ?? "";
         }
 
         return $list;

+ 131 - 0
app/Service/StatisticService.php

@@ -7,9 +7,11 @@ use App\Model\DailyDwOrderDetails;
 use App\Model\DailyPwOrderDetails;
 use App\Model\Device;
 use App\Model\Employee;
+use App\Model\EmployeeDepartPermission;
 use App\Model\ExpenseClaimsDetails;
 use App\Model\Fee;
 use App\Model\Item;
+use App\Model\ItemDetails;
 use App\Model\MonthlyDdOrder;
 use App\Model\MonthlyDdOrderDetails;
 use App\Model\MonthlyPsOrder;
@@ -924,4 +926,133 @@ class StatisticService extends StatisticCommonService
     }
 
 
+    public function enterpriseRdStatistic($data, $user){
+        $model = Item::TopClear($user,$data);
+        $model = $model->where('del_time',0)
+            ->select(Item::$report_field_1)
+            ->orderby('id', 'desc');
+
+        if(! empty($data['title'])) $model->where('title', 'LIKE', '%'.$data['title'].'%');
+        if(! empty($data['code'])) $model->where('code', 'LIKE', '%'.$data['code'].'%');
+        if(! empty($data['state'])) $model->where('state', $data['state']);
+
+        $list = $model->get()->toArray();
+        $list = $this->fillEnterpriseRdStatistic($list, $data, $user);
+
+        return [true, $list];
+    }
+
+    private function fillEnterpriseRdStatistic($list, $data, $user){
+        //项目实际支出 项目费用单
+        $expense = ExpenseClaimsDetails::Clear($user, $data);
+        $expense_map = $expense->where('del_time', 0)
+            ->whereIn('item_id', array_unique(array_column($list,'id')))
+            ->selectRaw('item_id, SUM(amount) as total_amount')
+            ->groupBy('item_id')
+            ->pluck('total_amount', 'item_id') // 这一步直接生成 item_id => total_amount 结构
+            ->toArray();
+
+        foreach ($list as $key => $value){
+            $list[$key]['actual_expenditure'] = $expense_map[$value['id']] ?? 0;
+            $start_time = $value['start_time'] ? date('Y-m-d', $value['start_time']) : '';
+            $end_time = $value['end_time'] ? date('Y-m-d', $value['end_time']) : '';
+            $list[$key]['time_range'] = $start_time . ' ' . $end_time;
+        }
+
+        return $list;
+    }
+
+    public function enterpriseRdManStatistic($data, $user){
+        $model = Employee::TopClear($user,$data);
+        $model = $model->where('del_time',0)
+            ->where('is_admin', '<=', Employee::IS_ADMIN_ONE)
+            ->select(Employee::$report_field)
+            ->orderBy('id','desc');
+
+        if(! empty($data['id_card'])) $model->where('id_card', 'LIKE', '%'.$data['id_card'].'%');
+        if(! empty($data['title'])) $model->where('title', 'LIKE', '%'.$data['title'].'%');
+        if(! empty($data['employee_type'])) $model->where('employee_type', $data['employee_type']);
+        if(isset($data['education'])) $model->where('education', $data['education']);
+
+        $list = $model->get()->toArray();
+        $list = $this->fillEnterpriseRdManStatistic($list, $data, $user);
+
+        return [true, $list];
+    }
+
+    private function fillEnterpriseRdManStatistic($list, $data, $user){
+        $man = EmployeeDepartPermission::from('employee_depart_permission as a')
+            ->join('depart as b', 'b.id', '=', 'a.depart_id')
+            ->whereIn('a.employee_id', array_unique(array_column($list,'id')))
+            ->select('a.employee_id', 'b.title')
+            ->get()->toArray();
+        $man_map = [];
+        foreach ($man as $value){
+            if(isset($man_map[$value['employee_id']])){
+                $man_map[$value['employee_id']] .= ',' . $value['title'];
+            }else{
+                $man_map[$value['employee_id']] = $value['title'];
+            }
+        }
+
+        foreach ($list as $key => $value){
+            $depart_title = $man_map[$value['id']] ?? "";
+            $list[$key]['position_new'] = $depart_title . '/' . $value['position'];
+            $list[$key]['employee_type_title'] = Employee::E_State_Type[$value['employee_type']] ?? '';
+            $list[$key]['education'] = Employee::Education[$value['education']] ?? '';
+        }
+
+        return $list;
+    }
+
+    public function enterpriseRdItemStatistic($data, $user){
+        // 1. 先声明表名和别名
+        $model = ItemDetails::from('item_details as i');
+        // 2. 再调用 TopClear
+        $model = $model->TopClear($user, $data);
+        $model = $model->from('item_details as i')
+            ->where('i.del_time', 0)
+            ->where('i.type', ItemDetails::type_one);
+        $model = $model->leftJoin('employee as e', 'i.data_id', '=', 'e.id');
+
+        $fields = ['e.id', 'e.title', 'e.education', 'e.major','e.p_title','i.item_id'];
+
+        $list = $model->select($fields)
+            ->orderBy('i.id', 'desc')
+            ->get()
+            ->toArray();
+
+        $list = $this->fillEnterpriseRdItemStatistic($list, $data, $user);
+
+        return [true, $list];
+    }
+
+    private function fillEnterpriseRdItemStatistic($list, $data, $user){
+        $man = EmployeeDepartPermission::from('employee_depart_permission as a')
+            ->join('depart as b', 'b.id', '=', 'a.depart_id')
+            ->whereIn('a.employee_id', array_unique(array_column($list,'id')))
+            ->select('a.employee_id', 'b.title')
+            ->get()->toArray();
+        $man_map = [];
+        foreach ($man as $value){
+            if(isset($man_map[$value['employee_id']])){
+                $man_map[$value['employee_id']] .= ',' . $value['title'];
+            }else{
+                $man_map[$value['employee_id']] = $value['title'];
+            }
+        }
+
+        $item_map = Item::whereIn('id',array_unique(array_column($list,'item_id')))
+            ->pluck('title','id')
+            ->toArray();
+        foreach ($list as $key => $value){
+            $depart_title = $man_map[$value['id']] ?? "";
+            $list[$key]['depart_title'] = $depart_title;
+            $item_title = $item_map[$value['item_id']] ?? "";
+            $list[$key]['item_title'] = $item_title;
+            $list[$key]['education'] = Employee::Education[$value['education']] ?? '';
+        }
+
+        return $list;
+    }
 }

+ 1 - 1
app/Service/SysMenuService.php

@@ -151,7 +151,7 @@ class SysMenuService extends Service
         $sysList = SysMenu::where('del_time',0)
             ->where('type', SysMenu::type_zero)
             ->where('tree_type', $tree_type)
-            ->select('title','icon','uri','parent_id','sort','id','is_authority', 'state')
+            ->select('title','icon','uri','parent_id','sort','id','is_authority', 'state', 'has_custom_field')
             ->orderBy('sort','desc')
             ->get()
             ->toArray();

+ 314 - 0
app/Service/TeamService.php

@@ -0,0 +1,314 @@
+<?php
+
+namespace App\Service;
+
+use App\Model\Employee;
+use App\Model\Team;
+use App\Model\TeamDetails;
+use Illuminate\Support\Facades\DB;
+
+class TeamService extends Service
+{
+    public function teamEdit($data,$user){
+        list($status,$msg) = $this->teamRule($data, $user, false);
+        if(!$status) return [$status,$msg];
+
+        try {
+            DB::beginTransaction();
+
+            $model = Team::where('id',$data['id'])->first();
+            $model->code = $data['code'] ?? '';
+            $model->title = $data['title'] ?? '';
+            $model->mark = $data['mark'] ?? '';
+            $model->state = $data['state'] ?? 0;
+            $model->charge_id = $data['charge_id'] ?? 0;
+            $model->save();
+
+            $time = time();
+            TeamDetails::where('del_time',0)
+                ->where('team_id', $model->id)
+                ->update(['del_time' => $time]);
+            $this->saveDetail($model->id, $time, $data);
+
+            DB::commit();
+        }catch (\Exception $exception){
+            DB::rollBack();
+            return [false,$exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    public function teamAdd($data,$user){
+        list($status,$msg) = $this->teamRule($data, $user);
+        if(!$status) return [$status,$msg];
+
+        try {
+            DB::beginTransaction();
+
+            $model = new Team();
+            $model->code = $data['code'] ?? '';
+            $model->title = $data['title'] ?? '';
+            $model->mark = $data['mark'] ?? '';
+            $model->state = $data['state'] ?? 0;
+            $model->charge_id = $data['charge_id'] ?? 0;
+            $model->crt_id = $user['id'];
+            $model->top_depart_id = $user['top_depart_id'];
+            $model->save();
+
+            $this->saveDetail($model->id, time(), $data);
+
+            DB::commit();
+        }catch (\Exception $exception){
+            DB::rollBack();
+            return [false,$exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    private function saveDetail($id, $time, $data){
+        if(! empty($data['man_list'])){
+            $unit = [];
+            foreach ($data['man_list'] as $value){
+                $unit[] = [
+                    'team_id' => $id,
+                    'data_id' => $value['data_id'],
+                    'top_depart_id' => $value['top_depart_id'],
+                    'crt_time' => $time,
+                ];
+            }
+            if(! empty($unit)) TeamDetails::insert($unit);
+        }
+    }
+
+    private function getDetail($id){
+        $data = TeamDetails::where('del_time',0)
+            ->where('team_id', $id)
+            ->get()->toArray();
+
+        $map = Employee::whereIn('id', array_unique(array_column($data,'data_id')))
+            ->select('title','id','number')
+            ->get()->toArray();
+        $map = array_column($map,null,'id');
+
+        $unit = [];
+        foreach ($data as $value){
+            $tmp = $map[$value['data_id']] ?? [];
+            $unit[] = [
+                'data_id' => $value['data_id'],
+                'data_title' => $tmp['title'],
+                'data_code' => $tmp['number'],
+            ];
+        }
+
+        $detail = [
+            'man_list' => $unit,
+        ];
+
+        return $detail;
+    }
+
+    public function teamDel($data){
+        if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
+
+        try {
+            DB::beginTransaction();
+            $time = time();
+
+            Team::where('del_time',0)
+                ->whereIn('id',$data['id'])
+                ->update(['del_time' => $time]);
+
+            TeamDetails::where('del_time',0)
+                ->whereIn('team_id', $data['id'])
+                ->update(['del_time' => $time]);
+
+            DB::commit();
+        }catch (\Exception $exception){
+            DB::rollBack();
+            return [false,$exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    public function teamDetail($data, $user){
+        if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
+        $customer = Team::where('del_time',0)
+            ->where('id',$data['id'])
+            ->first();
+        if(empty($customer)) return [false,'团队不存在或已被删除'];
+        $customer = $customer->toArray();
+        $customer['crt_name'] = Employee::where('id',$customer['crt_id'])->value('title');
+        $customer['charge_name'] = Employee::where('id',$customer['charge_id'])->value('title');
+        $customer['crt_time'] = $customer['crt_time'] ? date("Y-m-d H:i:s",$customer['crt_time']): '';
+        $customer['state_title'] = Employee::State_Type[$customer['state']] ?? '';
+
+        $details = $this->getDetail($data['id']);
+        $customer = array_merge($customer, $details);
+
+        return [true, $customer];
+    }
+
+    public function teamCommon($data,$user, $field = []){
+        if(empty($field)) $field = Team::$field;
+
+        $model = Team::TopClear($user,$data);
+        $model = $model->where('del_time',0)
+            ->select($field)
+            ->orderby('id', 'desc');
+
+        if(! empty($data['title'])) $model->where('title', 'LIKE', '%'.$data['title'].'%');
+        if(! empty($data['code'])) $model->where('code', 'LIKE', '%'.$data['code'].'%');
+        if(! empty($data['id'])) $model->whereIn('id', $data['id']);
+        if(isset($data['state'])) $model->where('state', $data['state']);
+        if(! empty($data['crt_time'][0]) && ! empty($data['crt_time'][1])) {
+            $return = $this->changeDateToTimeStampAboutRange($data['crt_time']);
+            $model->where('crt_time','>=',$return[0]);
+            $model->where('crt_time','<=',$return[1]);
+        }
+
+        return $model;
+    }
+
+    public function teamList($data,$user){
+        $model = $this->teamCommon($data, $user);
+        $list = $this->limit($model,'',$data);
+        $list = $this->fillData($list);
+
+        return [true, $list];
+    }
+
+    public function teamRule(&$data, $user, $is_add = true){
+        if(empty($data['code'])) return [false, '团队编码不能为空'];
+        if(! is_numeric($data['code'])) return [false, '团队编码请输入数字'];
+        if(empty($data['title'])) return [false, '团队名称不能为空'];
+        if(! isset($data['state'])) return [false, '状态不能为空'];
+        if(! isset(Team::State_Type[$data['state']])) return [false, '状态不存在'];
+        if(empty($data['man_list'])) return [false, '人员不能为空'];
+        foreach ($data['man_list'] as $key => $value){
+            if(empty($value['data_id'])) return [false, '人员不能为空'];
+            $data['man_list'][$key]['top_depart_id'] = $user['top_depart_id'];
+        }
+        list($status, $msg) = $this->checkArrayRepeat($data['man_list'],'data_id','人员');
+        if(! $status) return [false, $msg];
+
+        if($is_add){
+            $bool = Team::where('code',$data['code'])
+                ->where('top_depart_id', $user['top_depart_id'])
+                ->where('del_time',0)
+                ->exists();
+        }else{
+            if(empty($data['id'])) return [false,'ID不能为空'];
+            $bool = Team::where('code',$data['code'])
+                ->where('top_depart_id', $user['top_depart_id'])
+                ->where('id','<>',$data['id'])
+                ->where('del_time',0)
+                ->exists();
+        }
+        if($bool) return [false, '团队编码已存在'];
+
+        return [true, ''];
+    }
+
+    public function fillData($data){
+        if(empty($data['data'])) return $data;
+
+        $emp = (new EmployeeService())->getEmployeeMap(array_unique(array_merge_recursive(array_column($data['data'],'charge_id'), array_column($data['data'],'crt_id'))));
+        foreach ($data['data'] as $key => $value){
+            $data['data'][$key]['crt_time'] = $value['crt_time'] ? date('Y-m-d H:i:s',$value['crt_time']) : '';
+            $data['data'][$key]['crt_name'] = $emp[$value['crt_id']] ?? '';
+            $data['data'][$key]['charge_name'] = $emp[$value['charge_id']] ?? '';
+            $data['data'][$key]['state_title'] = Team::State_Type[$value['state']] ?? "";
+        }
+
+        return $data;
+    }
+
+    /**
+     * 填充项目导出数据(主表与明细平铺)
+     */
+    public function fillDataForExport($data, $column, $user, &$return)
+    {
+        if (empty($data)) return;
+
+        $dataArray = is_array($data) ? $data : $data->toArray();
+        $mainIds = array_column($dataArray, 'id');
+
+        // 1. 获取详情映射 [team_id => [ [type_title=>..., code_2=>..., title=>...], ... ]]
+        $detailsMap = $this->getDetailsMap($mainIds, $user);
+
+        // 2. 获取主表状态映射
+        $stateMap = \App\Model\Team::State_Type;
+
+        $empMap = Employee::whereIn('id', array_unique(array_column($dataArray,'charge_id')))->get()->keyBy('id');
+
+        foreach ($dataArray as $main) {
+            $teamId = $main['id'];
+            $details = $detailsMap[$teamId] ?? [];
+
+            // 3. 提取并格式化主表信息
+            $mainInfo = $main;
+            $mainInfo['state_title'] = $stateMap[$main['state'] ?? ''] ?? '';
+
+            $tmpEmp = $empMap[$main['charge_id']] ?? null;
+            $mainInfo['charge_number'] = $tmpEmp ? $tmpEmp->number : '';
+            $mainInfo['charge_name'] = $tmpEmp ? $tmpEmp->title : '';
+
+            if (empty($details)) {
+                // 如果单据没有明细,保底出一行
+                $tempRow = [];
+                foreach ($column as $col) {
+                    $tempRow[] = $mainInfo[$col] ?? '';
+                }
+                $return[] = $tempRow;
+            } else {
+                // 4. 核心平铺逻辑:每一行明细都带上主表信息
+                foreach ($details as $sub) {
+                    // 合并主表数据和详情数据(sub 中包含 type_title, code_2, title 等)
+                    $fullRowData = array_merge($mainInfo, $sub);
+
+                    $tempRow = [];
+                    foreach ($column as $col) {
+                        $tempRow[] = $fullRowData[$col] ?? '';
+                    }
+                    $return[] = $tempRow;
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取明细聚合映射表
+     */
+    public function getDetailsMap($mainIds, $user)
+    {
+        $details = TeamDetails::where('del_time', 0)
+            ->whereIn('team_id', $mainIds)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->get();
+
+        if ($details->isEmpty()) return [];
+
+        // 1. 批量提取关联 ID
+        $empIds = $details->pluck('data_id')->unique();
+
+        // 2. 批量获取档案 Map
+        $empMap = Employee::whereIn('id', $empIds)->get()->keyBy('id');
+
+        $res = [];
+        foreach ($details as $team) {
+            $detailRow = [];
+
+            $emp = $empMap[$team->data_id] ?? null;
+            $detailRow['code_2'] = $emp ? $emp->number : '';
+            $detailRow['title_2']  = $emp ? $emp->title : '';
+
+            // 归档到对应的主表 ID 下
+            $res[$team->team_id][] = $detailRow;
+        }
+
+        return $res;
+    }
+}

+ 94 - 0
config/excel/team.php

@@ -0,0 +1,94 @@
+<?php
+return [
+    "name" => "团队档案",
+    "array" => [
+        [
+            'key' =>'code',
+            'export' =>'code',
+            'value' => '团队编码',
+            'required' => true,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填'
+        ],
+        [
+            'key' =>'title',
+            'export' =>'title',
+            'value' => '团队名称',
+            'required' => true,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填'
+        ],
+        [
+            'key' =>'charge_id',
+            'export' =>'charge_number',
+            'value' => '负责人工号',
+            'required' => false,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '选填(填写负责人工号)'
+        ],
+        [
+            'key' =>'employee_title',
+            'export' =>'charge_name',
+            'value' => '负责人名称',
+            'required' => false,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '选填(便于操作人查看编码对应的名称)'
+        ],
+        [
+            'key' =>'state',
+            'export' =>'state_title',
+            'value' => '状态',
+            'required' => true,
+            'is_main' => true,
+            'default' => \App\Model\Team::TYPE_ONE,
+            'unique' => false,
+            'enums' => array_values(\App\Model\Team::State_Type),
+            'comments' => '必填'
+        ],
+        [
+            'key' =>'mark',
+            'export' =>'mark',
+            'value' => '团队描述',
+            'required' => false,
+            'is_main' => true,
+            'unique' => false,
+            'enums' => [],
+            'default' => "",
+            'comments' => ""
+        ],
+        [
+            'key' =>'code_2',
+            'export' =>'code_2',
+            'value' => '人员工号',
+            'required' => true,
+            'is_main' => false,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(人员的工号)'
+        ],
+        [
+            'key' =>'title_2',
+            'export' =>'title_2',
+            'value' => '人员名称',
+            'required' => false,
+            'is_main' => false,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '选填(便于操作人查看编码对应的名称)'
+        ],
+    ]
+];

+ 11 - 8
routes/api.php

@@ -64,6 +64,10 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     $route->any('employeeEditImg', 'Api\EmployeeController@employeeEditImg')->middleware('OssFileDeal');
     $route->any('getEmployeeImg', 'Api\EmployeeController@getEmployeeImg');
 
+    //公司设置
+    $route->any('companySet', 'Api\EmployeeController@companySet');
+    $route->any('companyDetail', 'Api\EmployeeController@companyDetail');
+
     //部门
     $route->any('departAdd', 'Api\EmployeeController@departAdd')->name('depart.add');
     $route->any('departEdit', 'Api\EmployeeController@departEdit')->name('depart.edit');
@@ -92,14 +96,6 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     $route->any('itemDel', 'Api\ItemController@itemDel');
     $route->any('itemDetail', 'Api\ItemController@itemDetail');
 
-    //中台新建角色 以及分配菜单权限
-    $route->any('roleMiddleGroundAdd', 'Api\RoleMiddleGroudController@roleAdd');
-    $route->any('roleMiddleGroundEdit', 'Api\RoleMiddleGroudController@roleEdit');
-    $route->any('roleMiddleGroundDel', 'Api\RoleMiddleGroudController@roleDel');
-    $route->any('roleMiddleGroundList', 'Api\RoleMiddleGroudController@roleList');
-    $route->any('roleMiddleGroundDetail', 'Api\RoleMiddleGroudController@roleDetail');
-    $route->any('roleMiddleGroundMenu', 'Api\RoleMiddleGroudController@roleMenu');
-
     //优先级
     $route->any('priorityList', 'Api\PriorityController@priorityList');
     $route->any('priorityEdit', 'Api\PriorityController@priorityEdit');
@@ -107,6 +103,13 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     $route->any('priorityDel', 'Api\PriorityController@priorityDel');
     $route->any('priorityDetail', 'Api\PriorityController@priorityDetail');
 
+    //团队
+    $route->any('teamList', 'Api\TeamController@teamList');
+    $route->any('teamEdit', 'Api\TeamController@teamEdit');
+    $route->any('teamAdd', 'Api\TeamController@teamAdd');
+    $route->any('teamDel', 'Api\TeamController@teamDel');
+    $route->any('teamDetail', 'Api\TeamController@teamDetail');
+
     //费用类型
     $route->any('feeAdd', 'Api\FeeController@feeAdd')->name('fee.add');
     $route->any('feeEdit', 'Api\FeeController@feeEdit')->name('fee.edit');