cqp 3 mesi fa
parent
commit
cc962bfdea

+ 18 - 0
app/Service/ExportFileService.php

@@ -155,6 +155,24 @@ class ExportFileService extends Service
         return [true, $this->saveExportData($return,$header)];
     }
 
+    public function monthPwOrder($ergs,$user){
+        // 导出数据
+        $return = [];
+        $header_default = $user['e_header_default'];
+        $column = array_column($header_default,'export');
+        $header = array_column($header_default,'value');
+
+        $service = new PersonWorkService();
+        $model = $service->monthlyPwOrderCommon($ergs, $user);
+
+        $model->chunk(500,function ($data) use(&$return, $service, $column){
+            // 直接处理这一批主表数据,将其与详情合并平铺
+            $service->fillDataForExport($data->toArray(), $column, $return);
+        });
+
+        return [true, $this->saveExportData($return,$header)];
+    }
+
     public function one($ergs, $user){
         $service = new EmployeeService();
         $model = $service->employeeCommon($ergs, $user);

+ 247 - 1
app/Service/ImportService.php

@@ -9,6 +9,7 @@ use App\Model\Employee;
 use App\Model\Fee;
 use App\Model\Item;
 use App\Model\ItemDetails;
+use App\Model\MonthlyPwOrder;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Maatwebsite\Excel\Facades\Excel;
@@ -22,6 +23,7 @@ class ImportService extends Service
         'device', // 设备
         'item', // 项目
         'fee', // 费用
+        'monthPwOrder', // 人员月度研发工时单
     ];
 
     public function getTableTitleXls($data,$user){
@@ -30,6 +32,7 @@ class ImportService extends Service
 
         //获取配置文件
         $fuc = $data['type'];
+        if (! method_exists(self::class, $fuc)) return [false, "导入文件获取不存在,请联系开发"];
         list($status,$return) = $this->$fuc($data,$user);
         list($msg,$filename) = $return;
         if(! $status) return [false, $msg];
@@ -85,6 +88,17 @@ class ImportService extends Service
         return [true, [$config_array, $filename]];
     }
 
+    private function monthPwOrder($data,$user){
+        $config = $this->getTableConfig($data['type']);
+        if(empty($config)) return [false, ['导入配置表头文件不存在','']];
+
+        $config_array = $config['array'] ?? [];
+        //生成下载文件
+        $filename =  $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
+
+        return [true, [$config_array, $filename]];
+    }
+
     //导入入口
     public function importAll($data,$user){
 //        //不超时
@@ -323,7 +337,7 @@ class ImportService extends Service
 
                 if (isset($detail_data_map[$key])) {
                     foreach ($detail_data_map[$key] as $d) {
-                        $all_detail_insert[] = array_merge($d, ['_code' => $main_tmp['code'], 'crt_time' => $time]);
+                        $all_detail_insert[] = array_merge($d, ['_code' => $main_tmp['code'], 'crt_time' => $time, 'top_depart_id' => $main_tmp['top_depart_id']]);
                     }
                 }
             }
@@ -731,6 +745,238 @@ class ImportService extends Service
         return false;
     }
 
+    // 人员月度工时单 ------------------------------
+    public function monthPwOrderImport($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, '导入数据不能为空'];
+
+        list($array, $error) = $this->checkCommon($array, $table_config);
+        if (!empty($error)) return [0, $error];
+
+        // 2. 详细校验 (这里会返回 update_map 和 dbEmps 映射)
+        list($error, $update_map, $dbEmps) = $this->monthPwOrderCheck($array, $user, $table_config);
+        if (!empty($error)) return [0, $error];
+
+        $keys = array_column($table_config, 'key');
+        $codeIdx = array_search('code', $keys);
+        $monthIdx = array_search('month', $keys);
+        $empIdx = array_search('employee_id', $keys);
+
+        // --- 步骤 1: 数据聚合分组 ---
+        // 将 Excel 数据按“单据”维度归类
+        $groups = [];
+        foreach ($array as $rowIndex => $row) {
+            $cCode = trim($row[$codeIdx] ?? '');
+            $cMonthTs = $row[$monthIdx]; // 校验阶段已转为时间戳
+
+            // 聚合 Key:有编码用编码,没编码用月份标记
+            $aggKey = $cCode ?: "MONTH_" . $cMonthTs;
+
+            if (!isset($groups[$aggKey])) {
+                $groups[$aggKey] = [
+                    'main_id'  => $update_map[$rowIndex] ?? 0, // 如果存在则是编辑
+                    'code'     => $cCode,
+                    'month'    => $cMonthTs,
+                    'details'  => []
+                ];
+            }
+
+            // 准备详情行数据
+            $detailTmp = [];
+            foreach ($table_config as $k => $conf) {
+                if (!$conf['is_main']) {
+                    $fieldKey = $conf['key'];
+                    $fieldVal = $row[$k];
+                    // 如果是人员,转换为 ID
+                    if ($fieldKey == 'employee_id') {
+                        $fieldVal = $dbEmps[$fieldVal] ?? 0;
+                    }
+                    $detailTmp[$fieldKey] = $fieldVal;
+                }
+            }
+            $groups[$aggKey]['details'][] = $detailTmp;
+        }
+
+        // --- 步骤 2: 开启事务写入 ---
+        DB::beginTransaction();
+        try {
+            $time = time();
+            foreach ($groups as $aggKey => $group) {
+                $mainId = $group['main_id'];
+
+                if ($mainId > 0) {
+                    // 删除旧详情
+                    DB::table('monthly_pw_order_details')->where('del_time',0)
+                        ->where('main_id', $mainId)
+                        ->update(['del_time' => $time]);
+                } else {
+                    // B. 新增逻辑
+                    $newCode = $this->generateBillNo([
+                        'top_depart_id' => $user['top_depart_id'],
+                        'type'          => MonthlyPwOrder::Order_type,
+                        'period'        => date("Ym", $group['month'])
+                    ]);
+
+                    $mainId = DB::table('monthly_pw_order')->insertGetId([
+                        'code'          => $newCode,
+                        'month'         => $group['month'],
+                        'top_depart_id' => $user['top_depart_id'],
+                        'crt_id'        => $user['id'],
+                        'crt_time'      => $time,
+                        'upd_time'      => $time,
+                        'del_time'      => 0
+                    ]);
+                }
+
+                // C. 批量插入详情
+                $insertDetails = [];
+                foreach ($group['details'] as $detail) {
+                    $detail['main_id']      = $mainId;
+                    $detail['top_depart_id']= $user['top_depart_id'];
+                    $detail['crt_time']     = $time;
+                    $detail['del_time']     = 0;
+                    $insertDetails[] = $detail;
+                }
+
+                if (!empty($insertDetails)) {
+                    DB::table('monthly_pw_order_details')->insert($insertDetails);
+                }
+            }
+
+            DB::commit();
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
+        }
+
+        return [true, ''];
+    }
+
+    private function monthPwOrderCheck(&$array, $user, $table_config)
+    {
+        $keys = array_column($table_config, 'key');
+        $codeIdx = array_search('code', $keys);
+        $monthIdx = array_search('month', $keys);
+        $empIdx = array_search('employee_id', $keys);
+        $startIdx = array_search('start_time', $keys);
+        $endIdx = array_search('end_time', $keys);
+
+        // 1. 预加载基础数据
+        $allEmpNumbers = array_filter(array_unique(array_column($array, $empIdx)));
+        $dbEmps = Employee::where('del_time', 0)
+            ->whereIn('number', $allEmpNumbers)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->pluck('id', 'number')->toArray();
+
+        $allCodes = array_filter(array_unique(array_column($array, $codeIdx)));
+        $dbOrders = DB::table('monthly_pw_order')
+            ->whereIn('code', $allCodes)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->get()->keyBy('code');
+
+        $existingMonths = DB::table('monthly_pw_order')
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->pluck('month')
+            ->toArray();
+        $existingMonthsMap = array_fill_keys($existingMonths, true);
+
+        $excelAggregator = [];
+        $errors = [];
+        $update_map = [];
+
+        foreach ($array as $rowIndex => $row) {
+            // 用户看到的行号(假设数据从第2行开始)
+            $displayLine = $rowIndex + 1;
+
+            $valCode = trim($row[$codeIdx] ?? '');
+            $valMonthRaw = trim($row[$monthIdx] ?? '');
+            $valEmp = trim($row[$empIdx] ?? '');
+            $valStartRaw = $row[$startIdx] ?? '';
+            $valEndRaw = $row[$endIdx] ?? '';
+
+            // --- A. 日期转换校验 ---
+            // 转换月份
+            list($mStatus, $valMonthTs) = $this->convertExcelCellToDate($valMonthRaw);
+            if (!$mStatus) {
+                $errors[] = "第{$displayLine}行:月份格式错误({$valMonthRaw})";
+                continue;
+            }
+            // 强制格式化为当月1号的时间戳,确保聚合逻辑一致
+            $valMonthTs = strtotime(date('Y-m-01', $valMonthTs));
+
+            // 转换开始日期
+            list($sStatus, $startTime) = $this->convertExcelCellToDate($valStartRaw);
+            if (!$sStatus) {
+                $errors[] = "第{$displayLine}行:开始日期格式错误";
+                continue;
+            }
+
+            // 转换结束日期
+            list($eStatus, $endTime) = $this->convertExcelCellToDate($valEndRaw);
+            if (!$eStatus) {
+                $errors[] = "第{$displayLine}行:结束日期格式错误";
+                continue;
+            }
+
+            $aggKey = $valCode ?: "NEW_ORDER_" . $valMonthTs;
+
+            // --- B. 校验单据与月份的一致性 ---
+            if ($valCode && isset($dbOrders[$valCode])) {
+                $dbOrder = $dbOrders[$valCode];
+                if ($dbOrder->month != $valMonthTs) {
+                    $errors[] = "第{$displayLine}行:单据编码[{$valCode}]对应月份为[" . date('Y-m', $dbOrder->month) . "],与导入月份不符";
+                }
+                $update_map[$rowIndex] = $dbOrder->id;
+            }
+
+            // --- C. 校验人员存在性与单据内唯一性 ---
+            if (!isset($dbEmps[$valEmp])) {
+                $errors[] = "第{$displayLine}行:人员工号[{$valEmp}]不存在";
+            } else {
+                if (isset($excelAggregator[$aggKey]['emps'][$valEmp])) {
+                    $errors[] = "第{$displayLine}行:人员[{$valEmp}]在同一单据中重复";
+                }
+                $excelAggregator[$aggKey]['emps'][$valEmp] = true;
+
+                if (isset($excelAggregator[$aggKey]['month_ts']) && $excelAggregator[$aggKey]['month_ts'] !== $valMonthTs) {
+                    $errors[] = "第{$displayLine}行:同组数据的月份不统一";
+                }
+                $excelAggregator[$aggKey]['month_ts'] = $valMonthTs;
+            }
+
+            // --- D. 逻辑校验:日期范围 ---
+            if ($startTime > $endTime) {
+                $errors[] = "第{$displayLine}行:开始日期大于结束日期";
+            } else {
+                $monthEndTs = strtotime("+1 month", $valMonthTs) - 1;
+                if ($startTime < $valMonthTs || $endTime > $monthEndTs) {
+                    $errors[] = "第{$displayLine}行:日期超出[ " . date('Y-m', $valMonthTs) . " ]范围";
+                }
+            }
+
+            // --- E. 月份唯一单据校验 (新增) ---
+            if (!$valCode) {
+                if (isset($existingMonthsMap[$valMonthTs])) {
+                    $errors[] = "第{$displayLine}行:月份[ " . date('Y-m', $valMonthTs) . " ]已存在单据,请填编码编辑";
+                }
+            }
+
+            // 将转换后的时间戳写回原数组,方便后续写入数据库时直接使用
+            $array[$rowIndex][$monthIdx] = $valMonthTs;
+            $array[$rowIndex][$startIdx] = $startTime;
+            $array[$rowIndex][$endIdx] = $endTime;
+        }
+
+        $error_string = !empty($errors) ? implode('|', $errors) : "";
+        return [$error_string, $update_map, $dbEmps];
+    }
+
     //公共校验 -----------------------------------------
     private function checkCommon($array, $table_config) {
         $error = [];

+ 7 - 3
app/Service/ItemService.php

@@ -79,6 +79,7 @@ class ItemService extends Service
                     'type' => $value['type'],
                     'data_id' => $value['data_id'],
                     'crt_time' => $time,
+                    'top_depart_id' => $value['top_depart_id'],
                 ];
             }
             if(! empty($unit)) ItemDetails::insert($unit);
@@ -92,6 +93,7 @@ class ItemService extends Service
                     'type' => $value['type'],
                     'data_id' => $value['data_id'],
                     'crt_time' => $time,
+                    'top_depart_id' => $value['top_depart_id'],
                 ];
             }
             if(! empty($receipt)) ItemDetails::insert($receipt);
@@ -233,6 +235,7 @@ class ItemService extends Service
     }
 
     public function itemRule(&$data, $user, $is_add = true){
+        $data['top_depart_id'] = $user['top_depart_id'];
         if(empty($data['code'])) return [false, '项目编码不能为空'];
         if(empty($data['title'])) return [false, '项目名称不能为空'];
         if(! empty($data['start_time'])) $data['start_time'] = $this->changeDateToDate($data['start_time']);
@@ -242,20 +245,21 @@ class ItemService extends Service
         if(empty($data['state'])) return [false, '项目状态不能为空'];
         if(! isset(Item::State_Type[$data['state']])) return [false, '项目状态不存在'];
         if(empty($data['man_list'])) return [false, '人员不能为空'];
-        foreach ($data['man_list'] as $value){
+        foreach ($data['man_list'] as $key => $value){
             if(empty($value['type'])) return [false, '类型不能为空'];
             if(empty($value['data_id'])) return [false, '人员不能为空'];
+            $data['man_list'][$key]['top_depart_id'] = $data['top_depart_id'];
         }
         list($status, $msg) = $this->checkArrayRepeat($data['man_list'],'data_id','人员');
         if(! $status) return [false, $msg];
         if(empty($data['device_list'])) return [false, '设备不能为空'];
-        foreach ($data['device_list'] as $value){
+        foreach ($data['device_list'] as $key => $value){
             if(empty($value['type'])) return [false, '类型不能为空'];
             if(empty($value['data_id'])) return [false, '数据ID不能为空'];
+            $data['device_list'][$key]['top_depart_id'] = $data['top_depart_id'];
         }
         list($status, $msg) = $this->checkArrayRepeat($data['device_list'],'data_id','设备');
         if(! $status) return [false, $msg];
-        $data['top_depart_id'] = $user['top_depart_id'];
 
         if($is_add){
             $bool = Item::where('code',$data['code'])

+ 55 - 44
app/Service/PersonWorkService.php

@@ -121,7 +121,6 @@ class PersonWorkService extends Service
         return $detail;
     }
 
-
     public function monthlyPwOrderDel($data){
         if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
 
@@ -251,56 +250,68 @@ class PersonWorkService extends Service
         return $data;
     }
 
-    public function getDetailsMap($item_id){
-        // 1. 获取明细数据,注意一定要选出 item_id
-        $data = ItemDetails::where('del_time', 0)
-            ->whereIn('item_id', $item_id)
-            ->select('item_id', 'data_id', 'type')
-            ->get();
+    public function fillDataForExport($data, $column, &$return)
+    {
+        if(empty($data)) return;
+
+        $mainIds = array_column($data, 'id');
+        // 获取详情映射 [main_id => [details...]]
+        $detailsMap = $this->getDetailsMap($mainIds);
 
-        // 2. 收集 ID 并按类型分类(去重以减少查询压力)
-        $manIds = $data->where('type', ItemDetails::type_one)->pluck('data_id')->unique()->toArray();
-        $devIds = $data->where('type', ItemDetails::type_two)->pluck('data_id')->unique()->toArray();
-
-        // 3. 一次性查出对应的映射 (假设人员表字段是 number, 设备表是 code)
-        // 注意:pluck('显示内容', 'ID')
-        $manMap = Employee::whereIn('id', $manIds)->pluck('number', 'id')->toArray();
-        $devMap = Device::whereIn('id', $devIds)->pluck('code', 'id')->toArray();
-
-        // 4. 按 item_id 分组处理成字符串
-        $result = [];
-        foreach ($item_id as $id) {
-            // 初始化每个 item_id 的默认结构
-            $result[$id] = [
-                'man' => '',
-                'device' => ''
+        // 默认空行模板
+        $defaultRow = array_fill_keys($column, '');
+
+        foreach ($data as $main) {
+            $mainId = $main['id'];
+            $details = $detailsMap[$mainId] ?? [];
+
+            // 提取主表信息
+            $mainInfo = [
+                'code'  => $main['code'],
+                'month' => $main['month'] ? date('Y-m', $main['month']) : '',
             ];
-        }
 
-        // 5. 遍历明细填充数据
-        $grouped = $data->groupBy('item_id');
-        foreach ($grouped as $itemId => $items) {
-            $mans = [];
-            $devices = [];
-
-            foreach ($items as $item) {
-                if ($item->type == ItemDetails::type_one) {
-                    if (isset($manMap[$item->data_id])) {
-                        $mans[] = $manMap[$item->data_id];
-                    }
-                } else {
-                    if (isset($devMap[$item->data_id])) {
-                        $devices[] = $devMap[$item->data_id];
-                    }
+            if (empty($details)) {
+                // 如果没有详情,至少导出一行主表信息(可选)
+                $return[] = array_merge($defaultRow, $mainInfo);
+            } else {
+                // 核心:遍历详情,每一行详情都合并主表信息
+                foreach ($details as $sub) {
+                    // 合并主表字段 + 详情字段
+                    $fullRow = array_merge($mainInfo, $sub);
+                    // 过滤掉不在导出列里的字段,并补充缺失列
+                    $return[] = array_merge($defaultRow, array_intersect_key($fullRow, $defaultRow));
                 }
             }
+        }
+    }
 
-            $result[$itemId] = [
-                'man' => implode(',', $mans),
-                'device' => implode(',', $devices)
+    public function getDetailsMap($main_ids)
+    {
+        // 获取详情
+        $details = MonthlyPwOrderDetails::where('del_time', 0)
+            ->whereIn('main_id', $main_ids)
+            ->get();
+
+        // 获取人员信息
+        $empIds = $details->pluck('employee_id')->unique();
+        $empMap = Employee::whereIn('id', $empIds)->get()->keyBy('id');
+
+        $res = [];
+        foreach ($details as $item) {
+            $tmpEmp = $empMap[$item->employee_id] ?? null;
+
+            // 组装每一行详情需要展示的字段
+            $res[$item->main_id][] = [
+                'employee_number' => $tmpEmp ? $tmpEmp->number : '',
+                'total_days'     => $item->total_days,
+                'rd_total_days'  => $item->rd_total_days,
+                'total_hours'    => $item->total_hours,
+                'rd_total_hours' => $item->rd_total_hours,
+                'start_time'     => $item->start_time ? date("Y-m-d", $item->start_time) : '',
+                'end_time'       => $item->end_time ? date("Y-m-d", $item->end_time) : '',
             ];
         }
-
-        return $result;
+        return $res; // 返回 [main_id => [detail_row, detail_row]]
     }
 }

+ 105 - 0
config/excel/monthPwOrder.php

@@ -0,0 +1,105 @@
+<?php
+return [
+    "name" => "人员月度工时单",
+    "array" => [
+        [
+            'key' =>'code',
+            'export' =>'code',
+            'value' => '单据编码',
+            'required' => false,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '(如果填写则是编辑)'
+        ],
+        [
+            'key' =>'month',
+            'export' =>'month',
+            'value' => '月份',
+            'required' => true,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(2026-02)'
+        ],
+        [
+            'key' =>'employee_id',
+            'export' =>'employee_number',
+            'value' => '人员',
+            'required' => true,
+            'is_main' => false,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(填写人员工号)'
+        ],
+        [
+            'key' =>'total_days',
+            'export' =>'total_days',
+            'value' => '出勤总天数',
+            'required' => true,
+            'is_main' => false,
+            'default' => 0,
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填'
+        ],
+        [
+            'key' =>'rd_total_days',
+            'export' =>'rd_total_days',
+            'value' => '研发出勤总天数',
+            'required' => true,
+            'is_main' => false,
+            'default' => 0,
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填'
+        ],
+        [
+            'key' =>'total_hours',
+            'export' =>'total_hours',
+            'value' => '出勤总工时',
+            'required' => true,
+            'is_main' => false,
+            'default' => 0,
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填'
+        ],
+        [
+            'key' =>'rd_total_hours',
+            'export' =>'rd_total_hours',
+            'value' => '研发总工时',
+            'required' => true,
+            'is_main' => false,
+            'default' => 0,
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填'
+        ],
+        [
+            'key' =>'start_time',
+            'export' =>'start_time',
+            'value' => '开始日期',
+            'required' => true,
+            'is_main' => false,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填'
+        ],
+        [
+            'key' =>'end_time',
+            'export' =>'end_time',
+            'value' => '结束日期',
+            'required' => true,
+            'is_main' => false,
+            'unique' => false,
+            'enums' => [],
+            'default' => "",
+            'comments' => ""
+        ],
+    ]
+];