cqp il y a 3 mois
Parent
commit
b78f299bfe

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

@@ -73,4 +73,71 @@ class DeviceWorkController extends BaseController
             return $this->json_return(201,$data);
         }
     }
+
+    public function dailyDwOrderEdit(Request $request)
+    {
+        $service = new DeviceWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyDwOrderEdit($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function dailyDwOrderAdd(Request $request)
+    {
+        $service = new DeviceWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyDwOrderAdd($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+
+    }
+
+    public function dailyDwOrderDel(Request $request)
+    {
+        $service = new DeviceWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyDwOrderDel($request->all());
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+
+    }
+
+    public function dailyDwOrderList(Request $request)
+    {
+        $service = new DeviceWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyDwOrderList($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function dailyDwOrderDetail(Request $request)
+    {
+        $service = new DeviceWorkService();
+        $user = $request->userData;
+        list($status,$data) = $service->dailyDwOrderDetail($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
 }

+ 17 - 0
app/Model/DailyDwOrder.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Model;
+
+class DailyDwOrder extends DataScopeBaseModel
+{
+    //设备日工时单
+    protected $table = "daily_dw_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_dw_order";
+}

+ 13 - 0
app/Model/DailyDwOrderDetails.php

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

+ 403 - 0
app/Service/DeviceWorkService.php

@@ -2,14 +2,18 @@
 
 namespace App\Service;
 
+use App\Model\DailyDwOrder;
+use App\Model\DailyDwOrderDetails;
 use App\Model\Device;
 use App\Model\Employee;
+use App\Model\Item;
 use App\Model\MonthlyDwOrder;
 use App\Model\MonthlyDwOrderDetails;
 use Illuminate\Support\Facades\DB;
 
 class DeviceWorkService extends Service
 {
+    //设备月工时单--------------------------------------------
     public function monthlyDwOrderEdit($data,$user){
         list($status,$msg) = $this->monthlyDwOrderRule($data, $user, false);
         if(!$status) return [$status,$msg];
@@ -308,4 +312,403 @@ class DeviceWorkService extends Service
         }
         return $res; // 返回 [main_id => [detail_row, detail_row]]
     }
+
+    //设备日工时单----------------------------------------------
+    public function dailyDwOrderEdit($data,$user){
+        list($status,$msg) = $this->dailyDwOrderRule($data, $user, false);
+        if(!$status) return [$status,$msg];
+
+        try {
+            DB::beginTransaction();
+
+            $model = DailyDwOrder::where('id',$data['id'])->first();
+            $model->item_id = $data['item_id'] ?? 0;
+            $model->save();
+
+            $time = time();
+            DailyDwOrderDetails::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 dailyDwOrderAdd($data,$user){
+        list($status,$msg) = $this->dailyDwOrderRule($data, $user);
+        if(!$status) return [$status,$msg];
+
+        try {
+            DB::beginTransaction();
+
+            $model = new DailyDwOrder();
+            $model->code = $this->generateBillNo([
+                'top_depart_id' => $user['top_depart_id'],
+                'type' => DailyDwOrder::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,
+                    'device_id' => $value['device_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)) DailyDwOrderDetails::insert($unit);
+        }
+    }
+
+    private function getDetailDaily($id){
+        $data = DailyDwOrderDetails::where('del_time',0)
+            ->where('main_id', $id)
+            ->select('device_id', 'start_time_hour', 'start_time_min', 'end_time_hour', 'end_time_min', 'total_work_min')
+            ->get()->toArray();
+
+        $id = array_column($data,'device_id');
+        $map = Device::whereIn('id',$id)
+            ->select('title','id','number')
+            ->get()
+            ->keyBy('id')
+            ->toArray();
+
+        foreach ($data as $key => $value){
+            $tmp = $map[$value['device_id']] ?? [];
+            $merge = [];
+            $merge['device_title'] = $tmp['title'];
+            $merge['device_code'] = $tmp['code'];
+            $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 dailyDwOrderDel($data){
+        if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
+
+        try {
+            DB::beginTransaction();
+            $time = time();
+
+            DailyDwOrder::where('del_time',0)
+                ->whereIn('id',$data['id'])
+                ->update(['del_time' => $time]);
+
+            DailyDwOrderDetails::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 dailyDwOrderDetail($data, $user){
+        if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
+        $customer = DailyDwOrder::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['order_time'] = $customer['order_time'] ? date("Y-m-d",$customer['order_time']): '';
+
+        $details = $this->getDetailDaily($data['id']);
+        $customer = array_merge($customer, $details);
+
+        return [true, $customer];
+    }
+
+    public function dailyDwOrderCommon($data,$user, $field = []){
+        if(empty($field)) $field = DailyDwOrder::$field;
+
+        $model = DailyDwOrder::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 dailyDwOrderList($data,$user){
+        $model = $this->dailyDwOrderCommon($data, $user);
+        $list = $this->limit($model,'',$data);
+        $list = $this->fillDataDaily($list);
+
+        return [true, $list];
+    }
+
+    public function dailyDwOrderRule(&$data, $user, $is_add = true){
+        if(empty($data['order_time'])) return [false, '单据日期不能为空'];
+        $data['order_time'] = $this->changeDateToDate($data['order_time']);
+        $orderTime = $data['order_time'];
+        $itemId = $data['item_id'] ?? 0;
+
+        if(empty($itemId)) return [false, '项目不能为空'];
+        $bool = Item::where('del_time',0)->where('id', $itemId)->exists();
+        if(!$bool) return [false, '项目不存在或已被删除'];
+
+        $data['top_depart_id'] = $user['top_depart_id'];
+        if(empty($data['details'])) return [false, '设备日工时单明细不能为空'];
+
+        // --- 1. 批量预获取人员信息,用于报错提示 ---
+        $allEmpIds = array_filter(array_unique(array_column($data['details'], 'device_id')));
+
+        $empDisplayMap = Device::whereIn('id', $allEmpIds)
+            ->get(['id', 'code', 'title'])
+            ->mapWithKeys(function($item){
+                return [$item->id => "[{$item->code}]{$item->title}"];
+            })->toArray();
+
+        // 2. 本次提交内部重叠记录
+        $internalOverlap = [];
+
+        foreach ($data['details'] as $key => $value){
+            $empId = $value['device_id'] ?? 0;
+            if(empty($empId)) return [false, '设备不能为空'];
+
+            $empName = $empDisplayMap[$empId] ?? "ID:{$empId}";
+
+            // 校验数字有效性 (修正为使用 $value)
+            $res = $this->checkNumber($value['start_time_hour'], 0, 'non-negative');
+            if(!$res['valid']) return [false, "设备{$empName}开始点:" . $res['error']];
+            $res = $this->checkNumber($value['start_time_min'], 0, 'non-negative');
+            if(!$res['valid']) return [false, "设备{$empName}开始分:" . $res['error']];
+            $res = $this->checkNumber($value['end_time_hour'], 0, 'non-negative');
+            if(!$res['valid']) return [false, "设备{$empName}结束点:" . $res['error']];
+            $res = $this->checkNumber($value['end_time_min'], 0, 'non-negative');
+            if(!$res['valid']) return [false, "设备{$empName}结束分:" . $res['error']];
+
+            $currentStart = $value['start_time_hour'] * 60 + $value['start_time_min'];
+            $currentEnd   = $value['end_time_hour'] * 60 + $value['end_time_min'];
+
+            if ($currentStart >= $currentEnd) {
+                return [false, "设备{$empName}:开始时间必须早于结束时间"];
+            }
+
+            // --- 3. 内部重叠校验(防止一次提交多行重复) ---
+            if (isset($internalOverlap[$empId])) {
+                foreach ($internalOverlap[$empId] as $period) {
+                    if ($currentStart < $period['e'] && $period['s'] < $currentEnd) {
+                        return [false, "设备{$empName}在本次提交的多行明细中时间段重叠"];
+                    }
+                }
+            }
+            $internalOverlap[$empId][] = ['s' => $currentStart, 'e' => $currentEnd];
+
+            $query = DB::table('daily_dw_order_details as d')
+                ->join('daily_dw_order as m', 'd.main_id', '=', 'm.id')
+                ->where('m.top_depart_id', $data['top_depart_id'])
+                ->where('m.order_time', $orderTime)
+                ->where('m.item_id', $itemId)
+                ->where('d.device_id', $empId)
+                ->where('m.del_time', 0)
+                ->where('d.del_time', 0);
+
+            if (!$is_add && !empty($data['id'])) {
+                $query->where('m.id', '<>', $data['id']);
+            }
+
+            $existingPeriods = $query->select('d.start_time_hour', 'd.start_time_min', 'd.end_time_hour', 'd.end_time_min')->get();
+
+            foreach ($existingPeriods as $p) {
+                $exStart = $p->start_time_hour * 60 + $p->start_time_min;
+                $exEnd   = $p->end_time_hour * 60 + $p->end_time_min;
+
+                if ($currentStart < $exEnd && $exStart < $currentEnd) {
+                    return [false, "设备{$empName}在该项目该日已有其他工时单创建重叠的时间段数据"];
+                }
+            }
+
+            $data['details'][$key]['top_depart_id'] = $data['top_depart_id'];
+        }
+
+        if(!$is_add){
+            if(empty($data['id'])) return [false,'ID不能为空'];
+            $bool = DailyDwOrder::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->getDwDailyDetailsMap($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 getDwDailyDetailsMap($mainIds, $mainData)
+    {
+        // 1. 获取设备工时子表记录
+        $details = DB::table('daily_dw_order_details')
+            ->where('del_time', 0)
+            ->whereIn('main_id', $mainIds)
+            ->get();
+
+        // 2. 提取关联 ID(设备 ID 和 项目 ID)
+        $deviceIds = $details->pluck('device_id')->unique();
+        $itemIds   = array_unique(array_column($mainData, 'item_id'));
+
+        // 3. 批量获取设备档案和项目档案
+        $deviceMap = DB::table('device')
+            ->whereIn('id', $deviceIds)
+            ->get(['id', 'title', 'code'])
+            ->keyBy('id');
+
+        $itemMap = DB::table('item')
+            ->whereIn('id', $itemIds)
+            ->get(['id', 'title', 'code'])
+            ->keyBy('id');
+
+        // 4. 预挂载主表的项目信息(item_code, item_title)
+        $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;
+        }
+
+        // 5. 循环子表,合并设备档案信息
+        foreach ($details as $item) {
+            $device = $deviceMap[$item->device_id] ?? null;
+
+            $detailRow = [
+                // 设备信息(对应 Excel 配置中的 key)
+                'device_code'  => $device ? $device->code : '',
+                'device_title' => $device ? $device->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;
+    }
 }

+ 18 - 0
app/Service/ExportFileService.php

@@ -299,6 +299,24 @@ class ExportFileService extends Service
         return [true, $this->saveExportData($return,$header)];
     }
 
+    public function dailyDwOrder($ergs,$user){
+        // 导出数据
+        $return = [];
+        $header_default = $user['e_header_default'];
+        $column = array_column($header_default,'export');
+        $header = array_column($header_default,'value');
+
+        $service = new DeviceWorkService();
+        $model = $service->dailyDwOrderCommon($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';

+ 203 - 62
app/Service/ImportService.php

@@ -4,6 +4,7 @@ namespace App\Service;
 
 use App\Exports\TableHeadExport;
 use App\Import\ImportAll;
+use App\Model\DailyDwOrder;
 use App\Model\DailyPwOrder;
 use App\Model\Depart;
 use App\Model\Device;
@@ -37,6 +38,7 @@ class ImportService extends Service
         'monthDdOrder', // 设备月度折旧单
         'ruleSet', // 规则配置单
         'dailyPwOrder', // 人员日工时单
+        'dailyDwOrder', // 设备日工时单
     ];
 
     public function getTableTitleXls($data,$user){
@@ -3058,40 +3060,186 @@ class ImportService extends Service
         return [$error_string, $update_map, $dbEmps, $dbItems];
     }
 
-    private function dailyPwOrderCheck1(&$array, $user, $table_config)
+    // 设备日工时单 ------------------------------
+    public function dailyDwOrderImport($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, $dbDevices, $dbItems) = $this->dailyDwOrderCheck($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);
+        $devIdx  = array_search('device_id', $keys); // 对应配置中的 device_id
         $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();
+        // 3. 数据聚合分组
+        $groups = [];
+        foreach ($array as $rowIndex => $row) {
+            $cCode = trim($row[$codeIdx] ?? '');
+            $cTimeTs = $row[$timeIdx];
+            $cItemCode = trim($row[$itemIdx] ?? '');
+            $cItemId = $dbItems[$cItemCode] ?? 0;
+
+            $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'][] = [
+                'device_id'       => $dbDevices[trim($row[$devIdx])] ?? 0, // 改为设备ID
+                '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_dw_order')->where('id', $mainId)->update([
+                        'item_id'  => $group['item_id'],
+                        'upd_time' => $time
+                    ]);
+                    DB::table('daily_dw_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'          => DailyDwOrder::Order_type,
+                        'period'        => date("Ym", $group['order_time'])
+                    ]);
+
+                    $mainId = DB::table('daily_dw_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_dw_order_details')->insert($insertDetails);
+                }
+            }
+            DB::commit();
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return [false, "失败:" . $e->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    private function dailyDwOrderCheck(&$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);
+        $devIdx  = array_search('device_id', $keys); // 设备编码索引
+        $startIdx = array_search('start_time', $keys);
+        $endIdx   = array_search('end_time', $keys);
+
+        $topDepartId = $user['top_depart_id'];
+
+        // 1. 批量预加载设备档案 (替换原本的人员逻辑)
+        $devCodes = array_filter(array_unique(array_column($array, $devIdx)));
+        $devModels = Device::where('del_time', 0)->where('top_depart_id', $topDepartId)
+            ->whereIn('code', $devCodes)->get(['id', 'code', 'title']);
+
+        $dbDevices = $devModels->pluck('id', 'code')->toArray(); // 以编号为Key查找ID
+        $devDisplayMap = $devModels->mapWithKeys(function($item){
+            return [$item->id => "[{$item->code}]{$item->title}"];
+        })->toArray();
 
         $itemCodes = array_filter(array_unique(array_column($array, $itemIdx)));
-        $dbItems = Item::where('del_time', 0)->where('top_depart_id', $user['top_depart_id'])
+        $dbItems = Item::where('del_time', 0)->where('top_depart_id', $topDepartId)
             ->whereIn('code', $itemCodes)->pluck('id', 'code')->toArray();
 
-        // 2. 预加载已有单据 (用于编辑判定)
+        // 2. 预加载设备已有单据
         $allCodes = array_filter(array_unique(array_column($array, $codeIdx)));
-        $dbOrders = DB::table('daily_pw_order')->where('top_depart_id', $user['top_depart_id'])
+        $dbOrders = DB::table('daily_dw_order')->where('top_depart_id', $topDepartId)
             ->whereIn('code', $allCodes)->where('del_time', 0)->get()->keyBy('code');
 
+        // 3. 跨单据预查
+        $allDates = [];
+        foreach ($array as $row) {
+            list($status, $ts) = $this->convertExcelCellToDate($row[$timeIdx] ?? '');
+            if ($status) $allDates[] = strtotime(date('Y-m-d', $ts));
+        }
+        $allDates = array_unique($allDates);
+
+        $dbExistingWork = DB::table('daily_dw_order_details as d')
+            ->join('daily_dw_order as m', 'd.main_id', '=', 'm.id')
+            ->where('m.top_depart_id', $topDepartId)
+            ->whereIn('m.order_time', $allDates)
+            ->whereIn('m.item_id', array_values($dbItems))
+            ->whereIn('d.device_id', array_values($dbDevices)) // 改为 device_id
+            ->where('m.del_time', 0)
+            ->where('d.del_time', 0)
+            ->whereNotIn('m.code', $allCodes)
+            ->select('m.order_time', 'm.item_id', 'd.device_id', 'd.start_time_hour', 'd.start_time_min', 'd.end_time_hour', 'd.end_time_min')
+            ->get()
+            ->groupBy(function($item) {
+                return $item->order_time . '_' . $item->item_id . '_' . $item->device_id;
+            });
+
         $errors = [];
         $update_map = [];
-        $excelBillMap = [];   // 用于校验 Excel 内部单据分组的一致性
-        $timeOverlapMap = []; // 用于同一单内的时间冲突重叠校验
+        $timeOverlapMap = [];
 
-        // 3. 全量循环检查
+        // 4. 全量循环检查
         foreach ($array as $rowIndex => $row) {
             $displayLine = $rowIndex + 1;
 
-            // A. 日期预处理
             list($status, $ts) = $this->convertExcelCellToDate($row[$timeIdx] ?? '');
             if (!$status) {
                 $errors[] = "第{$displayLine}行:单据日期格式错误";
@@ -3103,78 +3251,71 @@ class ImportService extends Service
             $valCode = trim($row[$codeIdx] ?? '');
             $valItemCode = trim($row[$itemIdx] ?? '');
             $itemId = $dbItems[$valItemCode] ?? 0;
-            $valEmpCode = trim($row[$empIdx] ?? '');
-            $empId = $dbEmps[$valEmpCode] ?? 0;
+            $valDevCode = trim($row[$devIdx] ?? '');
+            $devId = $dbDevices[$valDevCode] ?? 0;
+            $devName = $devDisplayMap[$devId] ?? "设备编号:{$valDevCode}";
 
-            // B. 基础存在性校验
-            if (!$itemId) {
-                $errors[] = "第{$displayLine}行:项目编号[{$valItemCode}]不存在";
-            }
-            if (!$empId) {
-                $errors[] = "第{$displayLine}行:人员工号[{$valEmpCode}]不存在";
-            }
+            if (!$itemId) $errors[] = "第{$displayLine}行:项目编号[{$valItemCode}]不存在";
+            if (!$devId) $errors[] = "第{$displayLine}行:设备编码[{$valDevCode}]不存在";
+            if (!$itemId || !$devId) continue;
 
-            // 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) . "],编辑模式不允许修改日期";
+                        $errors[] = "第{$displayLine}行:单据[{$valCode}]原日期不符,编辑模式不允许修改日期";
                     }
                     $update_map[$rowIndex] = $dbOrder->id;
                 } else {
-                    // 填了单号但数据库找不到:直接拦截,不准其作为“新增”
-                    $errors[] = "第{$displayLine}行:单据编号[{$valCode}]在系统中不存在,若要新增请清空单号列";
+                    $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 ($sH === null || $sM === null || $eH === null || $eM === null) {
+                $errors[] = "第{$displayLine}行:时间段格式非法";
+                continue;
+            }
 
-                if ($currentStart >= $currentEnd) {
-                    $errors[] = "第{$displayLine}行:开始时间[{$row[$startIdx]}]必须早于结束时间[{$row[$endIdx]}]";
-                } else {
-                    // 时间冲突重叠检测
-                    if ($empId > 0) {
-                        if (!isset($timeOverlapMap[$aggKey][$empId])) {
-                            $timeOverlapMap[$aggKey][$empId] = [];
-                        }
+            $currentStart = $sH * 60 + $sM;
+            $currentEnd   = $eH * 60 + $eM;
 
-                        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];
+            if ($currentStart >= $currentEnd) {
+                $errors[] = "第{$displayLine}行:设备{$devName}开始时间需早于结束时间";
+                continue;
+            }
+
+            $checkKey = $ts . "_" . $itemId . "_" . $devId;
+
+            // 跨单据检查
+            if (isset($dbExistingWork[$checkKey])) {
+                foreach ($dbExistingWork[$checkKey] as $p) {
+                    $exStart = $p->start_time_hour * 60 + $p->start_time_min;
+                    $exEnd   = $p->end_time_hour * 60 + $p->end_time_min;
+                    if ($currentStart < $exEnd && $exStart < $currentEnd) {
+                        $errors[] = "第{$displayLine}行:设备{$devName}在该项目当日已有记录重叠";
+                        break;
                     }
                 }
             }
+
+            // Excel内部检查
+            if (isset($timeOverlapMap[$checkKey])) {
+                foreach ($timeOverlapMap[$checkKey] as $period) {
+                    if ($currentStart < $period['e'] && $period['s'] < $currentEnd) {
+                        $errors[] = "第{$displayLine}行:设备{$devName}与表格内其他行重叠";
+                        break;
+                    }
+                }
+            }
+
+            $timeOverlapMap[$checkKey][] = ['s' => $currentStart, 'e' => $currentEnd];
         }
 
         $error_string = !empty($errors) ? implode('|', $errors) : "";
-        return [$error_string, $update_map, $dbEmps, $dbItems];
+        return [$error_string, $update_map, $dbDevices, $dbItems];
     }
 
     /**

+ 94 - 0
config/excel/dailyDwOrder.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' =>'device_id',
+            'export' =>'device_code',
+            'value' => '设备编码',
+            'required' => true,
+            'is_main' => false,
+            'default' => "",
+            'unique' => false,
+            'enums' => [],
+            'comments' => '必填(填写设备编码)'
+        ],
+        [
+            'key' =>'device_title',
+            'export' =>'device_title',
+            'value' => '设备名称',
+            'required' => false,
+            '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

@@ -132,5 +132,12 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     $route->any('dailyPwOrderAdd', 'Api\PersonWorkController@dailyPwOrderAdd');
     $route->any('dailyPwOrderDel', 'Api\PersonWorkController@dailyPwOrderDel');
     $route->any('dailyPwOrderDetail', 'Api\PersonWorkController@dailyPwOrderDetail');
+
+    //设备日工时单
+    $route->any('dailyDwOrderList', 'Api\DeviceWorkController@dailyDwOrderList');
+    $route->any('dailyDwOrderEdit', 'Api\DeviceWorkController@dailyDwOrderEdit');
+    $route->any('dailyDwOrderAdd', 'Api\DeviceWorkController@dailyDwOrderAdd');
+    $route->any('dailyDwOrderDel', 'Api\DeviceWorkController@dailyDwOrderDel');
+    $route->any('dailyDwOrderDetail', 'Api\DeviceWorkController@dailyDwOrderDetail');
 });