cqp 3 bulan lalu
induk
melakukan
5154ffa6a9

+ 67 - 0
app/Http/Controllers/Api/PersonWorkController.php

@@ -73,4 +73,71 @@ class PersonWorkController extends BaseController
             return $this->json_return(201,$data);
         }
     }
+
+    public function dailyPwOrderEdit(Request $request)
+    {
+        $service = new PersonWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyPwOrderEdit($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function dailyPwOrderAdd(Request $request)
+    {
+        $service = new PersonWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyPwOrderAdd($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+
+    }
+
+    public function dailyPwOrderDel(Request $request)
+    {
+        $service = new PersonWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyPwOrderDel($request->all());
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+
+    }
+
+    public function dailyPwOrderList(Request $request)
+    {
+        $service = new PersonWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyPwOrderList($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function dailyPwOrderDetail(Request $request)
+    {
+        $service = new PersonWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyPwOrderDetail($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
 }

+ 17 - 0
app/Model/DailyPwOrder.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Model;
+
+class DailyPwOrder extends DataScopeBaseModel
+{
+    //人员日工时单
+    protected $table = "daily_pw_order"; //指定表
+    const CREATED_AT = 'crt_time';
+    const UPDATED_AT = 'upd_time';
+    protected $dateFormat = 'U';
+    const employee_column = "crt_id";
+
+    public static $field = ['id','code','crt_id','crt_time','order_time', 'item_id'];
+
+    const Order_type = "daily_pw_order";
+}

+ 13 - 0
app/Model/DailyPwOrderDetails.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Model;
+
+class DailyPwOrderDetails extends DataScopeBaseModel
+{
+    //人员日工时单详细
+    protected $guarded = [];
+    protected $table = "daily_pw_order_details"; //指定表
+    const CREATED_AT = 'crt_time';
+    const UPDATED_AT = 'upd_time';
+    protected $dateFormat = 'U';
+}

+ 2 - 2
app/Service/EmployeeService.php

@@ -773,8 +773,8 @@ class EmployeeService extends Service
     }
 
     public function getEmployeeMap($employee_ids){
-        if(empty($employee_ids)) return [];
-        if(! is_array($employee_ids)) $employee_ids = [$employee_ids];
+        $employee_ids = array_filter((array)$employee_ids);
+        if (empty($employee_ids)) return [];
 
         return Employee::whereIn('id', $employee_ids)
             ->pluck('title', 'id')

+ 18 - 0
app/Service/ExportFileService.php

@@ -281,6 +281,24 @@ class ExportFileService extends Service
         return [true, $this->saveExportData($return,$header)];
     }
 
+    public function dailyPwOrder($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->dailyPwOrderCommon($ergs, $user);
+
+        $model->chunk(500,function ($data) use(&$return, $service, $column){
+            // 直接处理这一批主表数据,将其与详情合并平铺
+            $service->fillDataForExportDaily($data->toArray(), $column, $return);
+        });
+
+        return [true, $this->saveExportData($return,$header)];
+    }
+
     public function saveExportData($data, $headers, $type = 'default',$file_name = ''){
         if(empty($file_name)) $file_name = self::$filename . "_". date("Y-m-d") . "_". rand(1000,9999);
         $filename =  $file_name . '.' . 'xlsx';

+ 269 - 0
app/Service/ImportService.php

@@ -4,6 +4,7 @@ namespace App\Service;
 
 use App\Exports\TableHeadExport;
 use App\Import\ImportAll;
+use App\Model\DailyPwOrder;
 use App\Model\Depart;
 use App\Model\Device;
 use App\Model\Employee;
@@ -35,6 +36,7 @@ class ImportService extends Service
         'monthPsOrder', // 人员月度工资单
         'monthDdOrder', // 设备月度折旧单
         'ruleSet', // 规则配置单
+        'dailyPwOrder', // 人员日工时单
     ];
 
     public function getTableTitleXls($data,$user){
@@ -2785,6 +2787,273 @@ class ImportService extends Service
         return [$error_string, $update_map, $dbItems];
     }
 
+    // 人员日工时单 ------------------------------
+    // 人员日工时单导入 ------------------------------
+    public function dailyPwOrderImport($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. 详细业务校验 (返回 update_map, 人员映射, 项目映射)
+        list($error, $update_map, $dbEmps, $dbItems) = $this->dailyPwOrderCheck($array, $user, $table_config);
+        if (!empty($error)) return [0, $error];
+
+        $keys = array_column($table_config, 'key');
+        $codeIdx = array_search('code', $keys);
+        $timeIdx = array_search('order_time', $keys);
+        $itemIdx = array_search('item_id', $keys);
+        $empIdx  = array_search('employee_id', $keys);
+        $startIdx = array_search('start_time', $keys);
+        $endIdx   = array_search('end_time', $keys);
+
+        // 3. 数据聚合分组
+        $groups = [];
+        foreach ($array as $rowIndex => $row) {
+            $cCode = trim($row[$codeIdx] ?? '');
+            $cTimeTs = $row[$timeIdx];
+            $cItemCode = trim($row[$itemIdx] ?? '');
+            $cItemId = $dbItems[$cItemCode] ?? 0;
+
+            // 聚合 Key:如果是更新则按主表ID聚类;如果是新增则按 日期+项目 聚类
+            $aggKey = ($update_map[$rowIndex] ?? 0) > 0
+                ? "UPDATE_" . $update_map[$rowIndex]
+                : "NEW_" . $cTimeTs . "_" . $cItemId;
+
+            if (!isset($groups[$aggKey])) {
+                $groups[$aggKey] = [
+                    'main_id'    => $update_map[$rowIndex] ?? 0,
+                    'code'       => $cCode,
+                    'order_time' => $cTimeTs,
+                    'item_id'    => $cItemId,
+                    'details'    => []
+                ];
+            }
+
+            // 解析时间
+            list($sH, $sM) = $this->parseTimeHourMin($row[$startIdx]);
+            list($eH, $eM) = $this->parseTimeHourMin($row[$endIdx]);
+
+            $groups[$aggKey]['details'][] = [
+                'employee_id'     => $dbEmps[trim($row[$empIdx])] ?? 0,
+                'start_time_hour' => $sH,
+                'start_time_min'  => $sM,
+                'end_time_hour'   => $eH,
+                'end_time_min'    => $eM,
+                'total_work_min'  => ($eH * 60 + $eM) - ($sH * 60 + $sM), // 计算分钟差
+            ];
+        }
+
+        // 4. 执行写入
+        DB::beginTransaction();
+        try {
+            $time = time();
+            foreach ($groups as $group) {
+                $mainId = $group['main_id'];
+
+                if ($mainId > 0) {
+                    // 编辑:更新主表(允许改项目)
+                    DB::table('daily_pw_order')->where('id', $mainId)->update([
+                        'item_id'  => $group['item_id'],
+                        'upd_time' => $time
+                    ]);
+                    // 逻辑删除旧明细
+                    DB::table('daily_pw_order_details')->where('main_id', $mainId)
+                        ->where('del_time', 0)
+                        ->update(['del_time' => $time]);
+                } else {
+                    // 新增:自动生成单号
+                    $newCode = $this->generateBillNo([
+                        'top_depart_id' => $user['top_depart_id'],
+                        'type'          => DailyPwOrder::Order_type,
+                        'period'        => date("Ym", $group['order_time'])
+                    ]);
+
+                    $mainId = DB::table('daily_pw_order')->insertGetId([
+                        'code'          => $newCode,
+                        'order_time'    => $group['order_time'],
+                        'item_id'       => $group['item_id'],
+                        'top_depart_id' => $user['top_depart_id'],
+                        'crt_id'        => $user['id'],
+                        'crt_time'      => $time,
+                    ]);
+                }
+
+                // 批量插入明细
+                $insertDetails = [];
+                foreach ($group['details'] as $detail) {
+                    $detail['main_id']       = $mainId;
+                    $detail['top_depart_id'] = $user['top_depart_id'];
+                    $detail['crt_time']      = $time;
+                    $detail['upd_time']      = $time;
+                    $detail['del_time']      = 0;
+                    $insertDetails[] = $detail;
+                }
+                if (!empty($insertDetails)) {
+                    DB::table('daily_pw_order_details')->insert($insertDetails);
+                }
+            }
+            DB::commit();
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return [false, "失败:" . $e->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    /**
+     * 人员日工时单:详细业务校验
+     */
+    private function dailyPwOrderCheck(&$array, $user, $table_config)
+    {
+        $keys = array_column($table_config, 'key');
+        $codeIdx = array_search('code', $keys);
+        $timeIdx = array_search('order_time', $keys);
+        $itemIdx = array_search('item_id', $keys);
+        $empIdx  = array_search('employee_id', $keys);
+        $startIdx = array_search('start_time', $keys);
+        $endIdx   = array_search('end_time', $keys);
+
+        // 1. 批量预加载基础档案
+        $empCodes = array_filter(array_unique(array_column($array, $empIdx)));
+        $dbEmps = Employee::where('del_time', 0)->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('number', $empCodes)->pluck('id', 'number')->toArray();
+
+        $itemCodes = array_filter(array_unique(array_column($array, $itemIdx)));
+        $dbItems = Item::where('del_time', 0)->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('code', $itemCodes)->pluck('id', 'code')->toArray();
+
+        // 2. 预加载已有单据 (用于编辑判定)
+        $allCodes = array_filter(array_unique(array_column($array, $codeIdx)));
+        $dbOrders = DB::table('daily_pw_order')->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('code', $allCodes)->where('del_time', 0)->get()->keyBy('code');
+
+        $errors = [];
+        $update_map = [];
+        $excelBillMap = [];   // 用于校验 Excel 内部单据分组的一致性
+        $timeOverlapMap = []; // 用于同一单内的时间冲突重叠校验
+
+        // 3. 全量循环检查
+        foreach ($array as $rowIndex => $row) {
+            $displayLine = $rowIndex + 1;
+
+            // A. 日期预处理
+            list($status, $ts) = $this->convertExcelCellToDate($row[$timeIdx] ?? '');
+            if (!$status) {
+                $errors[] = "第{$displayLine}行:单据日期格式错误";
+                continue;
+            }
+            $ts = strtotime(date('Y-m-d', $ts));
+            $array[$rowIndex][$timeIdx] = $ts;
+
+            $valCode = trim($row[$codeIdx] ?? '');
+            $valItemCode = trim($row[$itemIdx] ?? '');
+            $itemId = $dbItems[$valItemCode] ?? 0;
+            $valEmpCode = trim($row[$empIdx] ?? '');
+            $empId = $dbEmps[$valEmpCode] ?? 0;
+
+            // B. 基础存在性校验
+            if (!$itemId) {
+                $errors[] = "第{$displayLine}行:项目编号[{$valItemCode}]不存在";
+            }
+            if (!$empId) {
+                $errors[] = "第{$displayLine}行:人员工号[{$valEmpCode}]不存在";
+            }
+
+            // C. 单号与编辑逻辑判定 (严密版)
+            if ($valCode !== '') {
+                if (isset($dbOrders[$valCode])) {
+                    $dbOrder = $dbOrders[$valCode];
+                    // 编辑模式:日期锁定,不允许改
+                    if ($dbOrder->order_time != $ts) {
+                        $errors[] = "第{$displayLine}行:单据[{$valCode}]原日期为[" . date('Y-m-d', $dbOrder->order_time) . "],编辑模式不允许修改日期";
+                    }
+                    $update_map[$rowIndex] = $dbOrder->id;
+                } else {
+                    // 填了单号但数据库找不到:直接拦截,不准其作为“新增”
+                    $errors[] = "第{$displayLine}行:单据编号[{$valCode}]在系统中不存在,若要新增请清空单号列";
+                }
+            }
+
+            // D. Excel 内部一致性:同一日期+项目 在同一表格中必须指向同一个 AggKey (即同一个单号或同一组新增)
+            $aggKey = ($update_map[$rowIndex] ?? 0) > 0 ? "UPDATE_" . $update_map[$rowIndex] : "NEW_" . $ts . "_" . $itemId;
+            $billKey = $ts . "_" . $itemId;
+            if (!isset($excelBillMap[$billKey])) {
+                $excelBillMap[$billKey] = $aggKey;
+            } elseif ($excelBillMap[$billKey] !== $aggKey) {
+                $errors[] = "第{$displayLine}行:日期和项目相同的数据在表格中指向了不统一的单据编号";
+            }
+
+            // E. 时间合法性与重叠校验
+            list($sH, $sM) = $this->parseTimeHourMin($row[$startIdx] ?? '');
+            list($eH, $eM) = $this->parseTimeHourMin($row[$endIdx] ?? '');
+
+            if ($sH === null || $sM === null) {
+                $errors[] = "第{$displayLine}行:开始时间格式非法或超出00:00-23:59范围";
+            } elseif ($eH === null || $eM === null) {
+                $errors[] = "第{$displayLine}行:结束时间格式非法或超出00:00-23:59范围";
+            } else {
+                $currentStart = $sH * 60 + $sM;
+                $currentEnd   = $eH * 60 + $eM;
+
+                if ($currentStart >= $currentEnd) {
+                    $errors[] = "第{$displayLine}行:开始时间[{$row[$startIdx]}]必须早于结束时间[{$row[$endIdx]}]";
+                } else {
+                    // 时间冲突重叠检测
+                    if ($empId > 0) {
+                        if (!isset($timeOverlapMap[$aggKey][$empId])) {
+                            $timeOverlapMap[$aggKey][$empId] = [];
+                        }
+
+                        foreach ($timeOverlapMap[$aggKey][$empId] as $period) {
+                            // 重叠判定公式:(StartA < EndB) AND (StartB < EndA)
+                            if ($currentStart < $period['e'] && $period['s'] < $currentEnd) {
+                                $errors[] = "第{$displayLine}行:人员[{$valEmpCode}]的时间段与该单据内已有记录重叠";
+                                break;
+                            }
+                        }
+                        // 记录该段分钟数,供后续行比对
+                        $timeOverlapMap[$aggKey][$empId][] = ['s' => $currentStart, 'e' => $currentEnd];
+                    }
+                }
+            }
+        }
+
+        $error_string = !empty($errors) ? implode('|', $errors) : "";
+        return [$error_string, $update_map, $dbEmps, $dbItems];
+    }
+
+    /**
+     * 解析并校验时间
+     */
+    private function parseTimeHourMin($val)
+    {
+        $h = -1; $m = -1;
+        if (strpos($val, ':') !== false) {
+            $parts = explode(':', $val);
+            $h = isset($parts[0]) ? (int)$parts[0] : -1;
+            $m = isset($parts[1]) ? (int)$parts[1] : -1;
+        } elseif (is_numeric($val)) {
+            $totalMinutes = (int)round($val * 1440);
+            $h = (int)floor($totalMinutes / 60);
+            $m = (int)($totalMinutes % 60);
+        }
+
+        if ($h < 0 || $h > 23 || $m < 0 || $m > 59) {
+            return [null, null];
+        }
+        return [$h, $m];
+    }
+
     //公共校验 -----------------------------------------
     private function checkCommon($array, $table_config) {
         $error = [];

+ 5 - 56
app/Service/ItemService.php

@@ -154,11 +154,13 @@ class ItemService extends Service
     }
 
     public function getItemMap($ids){
-        if(empty($ids)) return [];
-        if(! is_array($ids)) $ids = [$ids];
+        $ids = array_filter((array)$ids);
+        if (empty($ids)) return [];
 
         return Item::whereIn('id', $ids)
-            ->pluck('title', 'id')
+            ->select('id', 'title', 'code')
+            ->get()
+            ->keyBy('id')
             ->toArray();
     }
 
@@ -294,59 +296,6 @@ class ItemService extends Service
         return $data;
     }
 
-    public function getDetailsMap1($item_id){
-        // 1. 获取明细数据,注意一定要选出 item_id
-        $data = ItemDetails::where('del_time', 0)
-            ->whereIn('item_id', $item_id)
-            ->select('item_id', 'data_id', 'type')
-            ->get();
-
-        // 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' => ''
-            ];
-        }
-
-        // 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];
-                    }
-                }
-            }
-
-            $result[$itemId] = [
-                'man' => implode(',', $mans),
-                'device' => implode(',', $devices)
-            ];
-        }
-
-        return $result;
-    }
-
     /**
      * 填充项目导出数据(主表与明细平铺)
      */

+ 343 - 0
app/Service/PersonWorkService.php

@@ -2,13 +2,17 @@
 
 namespace App\Service;
 
+use App\Model\DailyPwOrder;
+use App\Model\DailyPwOrderDetails;
 use App\Model\Employee;
+use App\Model\Item;
 use App\Model\MonthlyPwOrder;
 use App\Model\MonthlyPwOrderDetails;
 use Illuminate\Support\Facades\DB;
 
 class PersonWorkService extends Service
 {
+    // 人员月工时单-------------------------------------------
     public function monthlyPwOrderEdit($data,$user){
         list($status,$msg) = $this->monthlyPwOrderRule($data, $user, false);
         if(!$status) return [$status,$msg];
@@ -307,4 +311,343 @@ class PersonWorkService extends Service
         }
         return $res; // 返回 [main_id => [detail_row, detail_row]]
     }
+
+    // 人员日工时单 ------------------------------------------------
+
+    public function dailyPwOrderEdit($data,$user){
+        list($status,$msg) = $this->dailyPwOrderRule($data, $user, false);
+        if(!$status) return [$status,$msg];
+
+        try {
+            DB::beginTransaction();
+
+            $model = DailyPwOrder::where('id',$data['id'])->first();
+            $model->item_id = $data['item_id'] ?? 0;
+            $model->save();
+
+            $time = time();
+            DailyPwOrderDetails::where('del_time',0)
+                ->where('main_id', $model->id)
+                ->update(['del_time' => $time]);
+            $this->saveDetailDaily($model->id, $time, $data);
+
+            DB::commit();
+        }catch (\Exception $exception){
+            DB::rollBack();
+            return [false,$exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    public function dailyPwOrderAdd($data,$user){
+        list($status,$msg) = $this->dailyPwOrderRule($data, $user);
+        if(!$status) return [$status,$msg];
+
+        try {
+            DB::beginTransaction();
+
+            $model = new DailyPwOrder();
+            $model->code = $this->generateBillNo([
+                'top_depart_id' => $user['top_depart_id'],
+                'type' => DailyPwOrder::Order_type,
+                'period' => date("Ym", $data['order_time'])
+            ]);
+            $model->order_time = $data['order_time'] ?? 0;
+            $model->item_id = $data['item_id'] ?? 0;
+            $model->crt_id = $user['id'];
+            $model->top_depart_id = $data['top_depart_id'];
+            $model->save();
+
+            $this->saveDetailDaily($model->id, time(), $data);
+
+            DB::commit();
+        }catch (\Exception $exception){
+            DB::rollBack();
+            return [false,$exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    private function saveDetailDaily($id, $time, $data){
+        if(! empty($data['details'])){
+            $unit = [];
+            foreach ($data['details'] as $value){
+                $unit[] = [
+                    'main_id' => $id,
+                    'employee_id' => $value['employee_id'],
+                    'start_time_hour' => $value['start_time_hour'],
+                    'start_time_min' => $value['start_time_min'],
+                    'end_time_hour' => $value['end_time_hour'],
+                    'end_time_min' => $value['end_time_min'],
+                    'total_work_min' => $value['total_work_min'],
+                    'crt_time' => $time,
+                    'top_depart_id' => $value['top_depart_id'],
+                ];
+            }
+            if(! empty($unit)) DailyPwOrderDetails::insert($unit);
+        }
+    }
+
+    private function getDetailDaily($id){
+        $data = DailyPwOrderDetails::where('del_time',0)
+            ->where('main_id', $id)
+            ->select('employee_id', 'start_time_hour', 'start_time_min', 'end_time_hour', 'end_time_min', 'total_work_min')
+            ->get()->toArray();
+
+        $id = array_column($data,'employee_id');
+        $map = Employee::whereIn('id', $id)->select('title','id','number')->get()->toArray();
+        $map = array_column($map,null,'id');
+
+        foreach ($data as $key => $value){
+            $tmp = $map[$value['employee_id']] ?? [];
+            $merge = [];
+            $merge['employee_title'] = $tmp['title'];
+            $merge['employee_number'] = $tmp['number'];
+            $data[$key] = array_merge($value, $merge);
+        }
+
+        $detail = [
+            'details' => $data,
+        ];
+
+        foreach ($detail as $key => $value) {
+            if (empty($value)) {
+                $detail[$key] = (object)[]; // 转成 stdClass 对象
+            }
+        }
+
+        return $detail;
+    }
+
+    public function dailyPwOrderDel($data){
+        if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
+
+        try {
+            DB::beginTransaction();
+            $time = time();
+
+            DailyPwOrder::where('del_time',0)
+                ->whereIn('id',$data['id'])
+                ->update(['del_time' => $time]);
+
+            DailyPwOrderDetails::where('del_time',0)
+                ->whereIn('main_id', $data['id'])
+                ->update(['del_time' => $time]);
+
+            DB::commit();
+        }catch (\Exception $exception){
+            DB::rollBack();
+            return [false,$exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    public function dailyPwOrderDetail($data, $user){
+        if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
+        $customer = DailyPwOrder::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['crt_time'] = $customer['crt_time'] ? date("Y-m-d H:i:s",$customer['crt_time']): '';
+        $item = Item::where('id', $customer['item_id'])->first();
+        $customer['item_title'] = $item->title;
+        $customer['item_code'] = $item->code;
+        $customer['month'] = $customer['month'] ? date("Y-m",$customer['month']): '';
+
+        $details = $this->getDetailDaily($data['id']);
+        $customer = array_merge($customer, $details);
+
+        return [true, $customer];
+    }
+
+    public function dailyPwOrderCommon($data,$user, $field = []){
+        if(empty($field)) $field = DailyPwOrder::$field;
+
+        $model = DailyPwOrder::Clear($user,$data);
+        $model = $model->where('del_time',0)
+            ->select($field)
+            ->orderby('id', 'desc');
+
+        if(! empty($data['code'])) $model->where('code', 'LIKE', '%'.$data['code'].'%');
+        if(! empty($data['id'])) $model->whereIn('id', $data['id']);
+        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 dailyPwOrderList($data,$user){
+        $model = $this->dailyPwOrderCommon($data, $user);
+        $list = $this->limit($model,'',$data);
+        $list = $this->fillDataDaily($list);
+
+        return [true, $list];
+    }
+
+    public function dailyPwOrderRule(&$data, $user, $is_add = true){
+        if(empty($data['order_time'])) return [false, '单据日期不能为空'];
+        $data['order_time'] = $this->changeDateToDate($data['order_time']);
+        if(empty($data['item_id'])) return [false, '项目不能为空'];
+        $bool = Item::where('del_time',0)->where('id', $data['item_id'])->exists();
+        if(! $bool) return [false, '项目不存在或已被删除'];
+
+        $data['top_depart_id'] = $user['top_depart_id'];
+        if(empty($data['details'])) return [false, '人员日工时单明细不能为空'];
+        foreach ($data['details'] as $key => $value){
+            if(empty($value['employee_id'])) return [false, '人员不能为空'];
+            if(empty($data['item_id'])) return [false,'项目不能为空'];
+            $res = $this->checkNumber($data['start_time_hour'],0,'non-negative');
+            if(! $res['valid']) return [false,'研发时段开始点:' . $res['error']];
+            $res = $this->checkNumber($data['start_time_min'],0,'non-negative');
+            if(! $res['valid']) return [false,'研发时段开始分:' . $res['error']];
+            $res = $this->checkNumber($data['end_time_hour'],0,'non-negative');
+            if(! $res['valid']) return [false,'研发时段结束点:' . $res['error']];
+            $res = $this->checkNumber($data['end_time_hour'],0,'non-negative');
+            if(! $res['valid']) return [false,'研发时段结束分:' . $res['error']];
+            $res = $this->checkNumber($data['total_work_min'],2,'positive');
+            if(! $res['valid']) return [false,'研发合计工时(分):' . $res['error']];
+            $data['details'][$key]['top_depart_id'] = $data['top_depart_id'];
+        }
+
+        if($is_add){
+
+        }else{
+            if(empty($data['id'])) return [false,'ID不能为空'];
+            $bool = DailyPwOrder::where('top_depart_id', $data['top_depart_id'])
+                ->where('id',$data['id'])
+                ->where('del_time',0)
+                ->exists();
+            if(! $bool) return [false, '人员日工时单不存在或已被删除'];
+        }
+
+        return [true, ''];
+    }
+
+    public function fillDataDaily($data){
+        if(empty($data['data'])) return $data;
+
+        $emp = (new EmployeeService())->getEmployeeMap(array_unique(array_column($data['data'],'crt_id')));
+        $item = (new ItemService())->getItemMap(array_unique(array_column($data['data'],'item_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]['order_time'] = $value['order_time'] ? date('Y-m-d',$value['order_time']) : '';
+            $data['data'][$key]['crt_name'] = $emp[$value['crt_id']] ?? '';
+            $item_tmp = $item[$value['item_id']] ?? [];
+            $data['data'][$key]['item_title'] = $item_tmp['title'] ?? '';
+            $data['data'][$key]['item_code'] = $item_tmp['code'] ?? '';
+        }
+
+        return $data;
+    }
+
+    public function fillDataForExportDaily($data, $column, &$return)
+    {
+        if (empty($data)) return;
+
+        $mainIds = array_column($data, 'id');
+        // 1. 获取详情及所有关联档案(项目、人员)的映射
+        $detailsMap = $this->getDailyDetailsMap($mainIds, $data);
+
+        foreach ($data as $main) {
+            $mainId = $main['id'];
+            $details = $detailsMap[$mainId] ?? [];
+
+            // 2. 提取并格式化主表共有信息
+            $mainInfo = [
+                'code'       => $main['code'],
+                'order_time' => !empty($main['order_time']) ? date('Y-m-d', $main['order_time']) : '',
+            ];
+
+            if (empty($details)) {
+                // 无明细时只导出一行主表信息
+                $tempRow = [];
+                foreach ($column as $col) {
+                    $tempRow[] = $mainInfo[$col] ?? '';
+                }
+                $return[] = $tempRow;
+            } else {
+                // 3. 平铺:将详情里的项目信息、人员信息与主表信息合并
+                foreach ($details as $sub) {
+                    $fullRowData = array_merge($mainInfo, $sub);
+                    $tempRow = [];
+                    foreach ($column as $col) {
+                        $tempRow[] = $fullRowData[$col] ?? '';
+                    }
+                    $return[] = $tempRow;
+                }
+            }
+        }
+    }
+
+    public function getDailyDetailsMap($mainIds, $mainData)
+    {
+        // 1. 获取所有子表记录
+        $details = DB::table('daily_pw_order_details')
+            ->where('del_time', 0)
+            ->whereIn('main_id', $mainIds)
+            ->get();
+
+        // 2. 提取所有关联 ID
+        $empIds = $details->pluck('employee_id')->unique();
+        $itemIds = array_unique(array_column($mainData, 'item_id')); // 从主表数组提取项目ID
+
+        // 3. 批量获取档案 Map
+        $empMap = DB::table('employee')
+            ->whereIn('id', $empIds)
+            ->get(['id', 'title', 'number'])
+            ->keyBy('id');
+
+        $itemMap = DB::table('item')
+            ->whereIn('id', $itemIds)
+            ->get(['id', 'title', 'code'])
+            ->keyBy('id');
+
+        // 4. 将主表的项目信息预先挂载到主表 ID 下,方便后续合并
+        $mainItemInfo = [];
+        foreach ($mainData as $m) {
+            $proj = $itemMap[$m['item_id']] ?? null;
+            $mainItemInfo[$m['id']] = [
+                'item_code'  => $proj ? $proj->code : '',
+                'item_title' => $proj ? $proj->title : '',
+            ];
+        }
+
+        $res = [];
+        if ($details->isEmpty()) {
+            // 如果没有详情,把项目信息返回去,确保主表能导出行
+            foreach ($mainItemInfo as $mId => $info) {
+                $res[$mId] = [];
+            }
+            return $res;
+        }
+
+        foreach ($details as $item) {
+            $emp = $empMap[$item->employee_id] ?? null;
+
+            // 组装明细行数据
+            $detailRow = [
+                // 人员信息
+                'employee_number' => $emp ? $emp->number : '',
+                'employee_title'  => $emp ? $emp->title : '',
+                // 时间信息
+                'start_time'      => sprintf('%02d:%02d', $item->start_time_hour, $item->start_time_min),
+                'end_time'        => sprintf('%02d:%02d', $item->end_time_hour, $item->end_time_min),
+                // 将主表的项目信息也塞进每一行详情里实现平铺
+                'item_code'       => $mainItemInfo[$item->main_id]['item_code'] ?? '',
+                'item_title'      => $mainItemInfo[$item->main_id]['item_title'] ?? '',
+            ];
+
+            $res[$item->main_id][] = $detailRow;
+        }
+
+        return $res;
+    }
 }

+ 94 - 0
config/excel/dailyPwOrder.php

@@ -0,0 +1,94 @@
+<?php
+return [
+    "name" => "人员日工时单",
+    "array" => [
+        [
+            'key' =>'code',
+            'export' =>'code',
+            'value' => '单据编码',
+            'required' => false,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '(如果填写则是编辑)'
+        ],
+        [
+            'key' =>'order_time',
+            'export' =>'order_time',
+            'value' => '单据日期',
+            'required' => true,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(2026-02-01)'
+        ],
+        [
+            'key' =>'item_id',
+            'export' =>'item_code',
+            'value' => '项目编号',
+            'required' => true,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(填写项目编号)'
+        ],
+        [
+            'key' =>'item_title',
+            'export' =>'item_title',
+            'value' => '项目名称',
+            'required' => true,
+            'is_main' => true,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '选填(便于操作人查看编码对应的名称)'
+        ],
+        [
+            'key' =>'employee_id',
+            'export' =>'employee_number',
+            'value' => '人员工号',
+            'required' => true,
+            'is_main' => false,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(填写人员工号)'
+        ],
+        [
+            'key' =>'employee_title',
+            'export' =>'employee_title',
+            'value' => '人员名称',
+            'required' => true,
+            'is_main' => false,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '选填(便于操作人查看编码对应的名称)'
+        ],
+        [
+            'key' =>'start_time',
+            'export' =>'start_time',
+            'value' => '开始时间段',
+            'required' => true,
+            'is_main' => false,
+            'default' => 0,
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(如 8:00)'
+        ],
+        [
+            'key' =>'end_time',
+            'export' =>'end_time',
+            'value' => '结束时间段',
+            'required' => true,
+            'is_main' => false,
+            'default' => 0,
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(如 9:00)'
+        ],
+    ]
+];

+ 7 - 0
routes/api.php

@@ -125,5 +125,12 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     $route->any('ruleSetDel', 'Api\RuleSetController@ruleSetDel');
     $route->any('ruleSetDetail', 'Api\RuleSetController@ruleSetDetail');
     $route->any('ruleSetCreate', 'Api\RuleSetController@ruleSetCreate');
+
+    //人员日工时单
+    $route->any('dailyPwOrderList', 'Api\PersonWorkController@dailyPwOrderList');
+    $route->any('dailyPwOrderEdit', 'Api\PersonWorkController@dailyPwOrderEdit');
+    $route->any('dailyPwOrderAdd', 'Api\PersonWorkController@dailyPwOrderAdd');
+    $route->any('dailyPwOrderDel', 'Api\PersonWorkController@dailyPwOrderDel');
+    $route->any('dailyPwOrderDetail', 'Api\PersonWorkController@dailyPwOrderDetail');
 });