Jelajahi Sumber

Merge remote-tracking branch 'origin/master'

gogs 1 bulan lalu
induk
melakukan
2b06596def

+ 2 - 1
app/Exports/ManMonthlyWorkHourMultipleSheetExport.php

@@ -21,7 +21,8 @@ class ManMonthlyWorkHourMultipleSheetExport implements WithMultipleSheets
             $sheets[] = new ManMonthlyWorkHourSheetExport(
                 (string)$monthName,
                 $config['data'] ?? [],
-                (int)($config['days'] ?? 30)
+                (int)($config['days'] ?? 30),
+                $config['company_name']
             );
         }
         return $sheets;

+ 4 - 2
app/Exports/ManMonthlyWorkHourSheetExport.php

@@ -15,12 +15,14 @@ class ManMonthlyWorkHourSheetExport implements WithEvents, WithTitle
     protected $monthName;
     protected $data;
     protected $daysInMonth;
+    protected $company_name;
 
-    public function __construct(string $monthName, array $data, int $daysInMonth)
+    public function __construct(string $monthName, array $data, int $daysInMonth, string $company_name)
     {
         $this->monthName   = $monthName;
         $this->data        = $data;
         $this->daysInMonth = $daysInMonth;
+        $this->company_name = $company_name;
     }
 
     public function title(): string
@@ -46,7 +48,7 @@ class ManMonthlyWorkHourSheetExport implements WithEvents, WithTitle
 
                 // --- 2. 第二行:单位行 ---
                 $sheet->mergeCells("A2:{$highestColumn}2");
-                $sheet->setCellValue('A2', "单位:");
+                $sheet->setCellValue('A2', "单位:" . $this->company_name);
                 $sheet->getStyle('A2')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
 
                 // --- 3. 第三行表头设置 ---

+ 28 - 2
app/Exports/ProjectDepreciationSheetExport.php

@@ -79,7 +79,7 @@ class ProjectDepreciationSheetExport implements WithEvents, WithTitle
                     $dataRowsCount = count($items);
                     foreach ($items as $index => $item) {
                         $sheet->setCellValue("A{$currentRow}", $index + 1);
-                        $sheet->setCellValue("B{$currentRow}", $item['name'] ?? '');
+                        $sheet->setCellValue("B{$currentRow}", $item['device_name'] ?? '');
                         $sheet->setCellValue("C{$currentRow}", $item['total_hours'] ?? 0);
                         $sheet->setCellValue("D{$currentRow}", $item['project_hours'] ?? 0);
                         $sheet->setCellValue("E{$currentRow}", ($item['ratio'] ?? 0) . '%');
@@ -103,7 +103,33 @@ class ProjectDepreciationSheetExport implements WithEvents, WithTitle
                     // 5. 合计行
                     $sheet->setCellValue("A{$currentRow}", "合计");
                     $sheet->mergeCells("A{$currentRow}:B{$currentRow}");
-                    // 这里可以填充合计逻辑...
+
+                    $total = 0;
+                    $totalProjectHours = 0;
+                    $totalOriginalValue = 0;
+                    $totalDepreciation = 0;
+                    $totalConfirmedDepreciation = 0;
+                    $totalAdjustAmount = 0;
+
+                    foreach ($items as $item) {
+                        $total += ($item['total_hours'] ?? 0);
+                        $totalProjectHours += ($item['project_hours'] ?? 0);
+                        $totalOriginalValue += ($item['original_value'] ?? 0);
+                        $totalDepreciation += ($item['depreciation'] ?? 0);
+                        $totalConfirmedDepreciation += ($item['confirmed_depreciation'] ?? 0);
+                        $totalAdjustAmount += ($item['adjust_amount'] ?? 0);
+                    }
+
+                    $sheet->setCellValue("C{$currentRow}", $total);
+                    $sheet->setCellValue("D{$currentRow}", $totalProjectHours);
+                    $sheet->setCellValue("F{$currentRow}", $totalOriginalValue);
+                    $sheet->setCellValue("G{$currentRow}", $totalDepreciation);
+                    $sheet->setCellValue("H{$currentRow}", $totalConfirmedDepreciation);
+                    $sheet->setCellValue("J{$currentRow}", $totalAdjustAmount);
+
+                    // 设置合计行样式:加粗
+                    $sheet->getStyle("A{$currentRow}:J{$currentRow}")->getFont()->setBold(true);
+
                     $currentRow++;
 
                     // 6. 备注

+ 6 - 12
app/Model/Item.php

@@ -28,7 +28,6 @@ class Item extends DataScopeBaseModel
         self::TYPE_THREE => '已完成',
     ];
 
-
     const Attr_TYPE_ONE = 1;
     const Attr_TYPE_TWO = 2;
     const Item_Attribute = [
@@ -39,20 +38,15 @@ class Item extends DataScopeBaseModel
     // 引入自定义字段预加载关系
     use HasCustomFields;
 
-    // 作用域 里面关联了客户自定义的表头数据
-    public function scopeWithCustomData($query, $menuId, $user)
+    // 作用域 里面关联了客户自定义的表头数据 列表页需要 :: withCustomData($user) 这个是预加载
+    public function scopeWithCustomData($query, $user)
     {
-        if (empty($menuId)) return $query;
-
         $top_depart_id = $user['top_depart_id'];
 
-        return $query->with(['customFieldValues' => function ($subQuery) use ($menuId, $top_depart_id) {
-            //只查询设置的id 和 自定义字段的值 还有业务单据/档案的id(这个必须要 预加载里没有主外键关联关系就查询失败)
-            $subQuery->select(['definition_id', 'field_value', 'model_id', 'field_type'])
-                ->where('top_depart_id', $top_depart_id)
-                ->whereHas('definition', function ($q) use ($menuId) {
-                    $q->where('menu_id', $menuId);
-                });
+        return $query->with(['customFields' => function ($subQuery) use ($top_depart_id) {
+            $subQuery->where('top_depart_id', $top_depart_id)
+                ->where('table_name', $this->table)
+                ->where('del_time', 0);
         }]);
     }
 }

+ 2 - 2
app/Model/SysOssTasks.php

@@ -12,7 +12,7 @@ class SysOssTasks extends Model
     const UPDATED_AT = 'upd_time';
     protected $dateFormat = 'U';
 
-    const type_one = 1;
-    const type_two = 2;
+    const type_one = 1; //删除
+    const type_two = 2; //新增
     const job = 'sync_oss_file';
 }

+ 89 - 3
app/Service/CustomFieldSettingService.php

@@ -4,8 +4,7 @@ namespace App\Service;
 
 use App\Jobs\ProcessOssTask;
 use App\Model\CustomFieldDefinitions;
-use App\Model\SysMenu;
-use App\Model\SysModules;
+use App\Model\CustomFieldValue;
 use App\Model\SysOssTasks;
 use Illuminate\Support\Facades\DB;
 
@@ -170,7 +169,7 @@ class CustomFieldSettingService extends Service
         return [true, $data];
     }
 
-    public static function syncCustomFieldData($id, $data, $user)
+    public static function syncCustomFieldData1($id, $data, $user)
     {
         $top_depart_id = $user['top_depart_id'];
         if (empty($data['menu_id'])) return [true, ''];
@@ -259,4 +258,91 @@ class CustomFieldSettingService extends Service
             }
         }
     }
+
+    public static function syncCustomFieldData($modelId, $tableName, $data, $user)
+    {
+        if(empty($tableName)) return [false, '表名不存在'];
+
+        $top_depart_id = $user['top_depart_id'];
+        $customFields = $data['custom_fields'] ?? [];
+        $time = time();
+        $pendingTasks = [];
+
+        // 1. 预校验:过滤掉空标签后,判断剩下的标签是否重复
+        $labels = array_filter(array_column($customFields, 'field_label'));
+        if (count($labels) !== count(array_unique($labels))) return [false, '自定义字段名称不能重复'];
+
+        try {
+            // 2. 获取数据库现有的旧数据(仅限未删除的)
+            $oldValues = CustomFieldValue::where('top_depart_id', $top_depart_id)
+                ->where('table_name', $tableName)
+                ->where('model_id', $modelId)
+                ->where('del_time', 0)
+                ->get()
+                ->keyBy('id');
+
+            $submittedIds = [];
+
+            // 3. 处理前端提交的数据
+            foreach ($customFields as $field) {
+                $fId    = (int)($field['id'] ?? 0);
+                $label  = trim((string)($field['field_label'] ?? ''));
+                $newVal = (string)($field['field_value'] ?? '');
+                $type   = (int)($field['field_type'] ?? 1);
+
+                if ($label === '') continue; // 标签为空的直接跳过,不保存
+
+                // --- OSS 附件逻辑 ---
+                if ($type === 2) {
+                    $oldVal = isset($oldValues[$fId]) ? $oldValues[$fId]->field_value : '';
+                    if ($newVal !== $oldVal) {
+                        if (!empty($oldVal)) $pendingTasks[] = ['type' => SysOssTasks::type_one, 'url' => $oldVal, 'top_depart_id' => $top_depart_id, 'crt_time' => $time];
+                        if (!empty($newVal)) $pendingTasks[] = ['type' => SysOssTasks::type_two, 'url' => $newVal, 'top_depart_id' => $top_depart_id, 'crt_time' => $time];
+                    }
+                }
+
+                // --- 保存/更新 ---
+                // 如果前端传了 ID,就按 ID 改;没传 ID,就新增(updateOrInsert 在 id 为 0 时会执行插入)
+                $saveData = [
+                    'top_depart_id' => $top_depart_id,
+                    'table_name'    => $tableName,
+                    'model_id'      => $modelId,
+                    'field_label'   => $label,
+                    'field_value'   => $newVal,
+                    'field_type'    => $type,
+                ];
+
+                if ($fId > 0) {
+                    CustomFieldValue::where('id', $fId)->update($saveData);
+                    $submittedIds[] = $fId;
+                } else {
+                    $saveData['crt_time'] = $time;
+                    $newId = CustomFieldValue::insertGetId($saveData);
+                    $submittedIds[] = $newId;
+                }
+            }
+
+            // 4. 清理:如果旧 ID 不在本次提交的 ID 列表中,说明被删除了
+            foreach ($oldValues as $oldId => $oldRow) {
+                if (!in_array($oldId, $submittedIds)) {
+                    // 如果删掉的是附件,加个删除任务
+                    if ($oldRow->field_type == 2 && !empty($oldRow->field_value)) {
+                        $pendingTasks[] = ['type' => SysOssTasks::type_one, 'url' => $oldRow->field_value, 'top_depart_id' => $top_depart_id, 'crt_time' => $time];
+                    }
+                    CustomFieldValue::where('id', $oldId)->update(['del_time' => $time]);
+                }
+            }
+
+            // 5. 触发 OSS 任务
+            if (!empty($pendingTasks)) {
+                SysOssTasks::insert($pendingTasks);
+                ProcessOssTask::dispatch()->onQueue(SysOssTasks::job);
+            }
+
+        } catch (\Throwable $exception) {
+            return [false, $exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
 }

+ 10 - 6
app/Service/ExportFileService.php

@@ -512,6 +512,9 @@ class ExportFileService extends Service
             return date('Y年m月', strtotime($item['order_date']));
         });
 
+        //获取公司基本信息
+        $company = EmployeeService::getCompanyDetail($user);
+
         foreach ($groupedByMonth as $monthName => $monthItems) {
             // A. 计算该月总天数
             // 转换 '2024年04月' 为 '2024-04' 获取天数
@@ -526,9 +529,9 @@ class ExportFileService extends Service
             $sheetRows = [];
             foreach ($groupedByUserItem as $key => $records) {
                 $first = $records->first();
-                // 初始化行:前两列是 项目编号 和 姓名
+                // 初始化行:前两列是 项目名称 和 姓名
                 $row = [
-                    $first['item_code'],
+                    $first['item_title'],
                     $first['employee_name']
                 ];
 
@@ -549,7 +552,8 @@ class ExportFileService extends Service
 
             $monthsData[$monthName] = [
                 'days' => (int)$daysInMonth,
-                'data' => $sheetRows
+                'data' => $sheetRows,
+                'company_name' => $company['title'] ?? '',
             ];
         }
 
@@ -574,10 +578,10 @@ class ExportFileService extends Service
         $finalExportData = [];
 
         foreach ($groupedData as $itemCode => $itemRecords) {
-            $projectName = $itemRecords->first()['item_title'] ?? '';
+            $firstRecord = $itemRecords->first();
+            $projectName = $firstRecord['item_title'];
 
-            // 2. 获取年份(从 order_month 字段提取,如 "2026-01" 取前4位)
-            $firstMonth = $firstRecord['month'] ?? date('Y-m');
+            $firstMonth = $firstRecord['month'];
             $year = substr($firstMonth, 0, 4);
 
             // 3. 构造新的 Key:年-项目 (例如: 2026-53code)

+ 8 - 8
app/Service/ItemService.php

@@ -19,7 +19,8 @@ class ItemService extends Service
         try {
             DB::beginTransaction();
 
-            $model = Item::where('id',$data['id'])->first();
+            $model = Item::where('id', $data['id'])->first();
+            $tableName = $model->getTable();
             $model->code = $data['code'] ?? '';
             $model->title = $data['title'] ?? '';
             $model->mark = $data['mark'] ?? "";
@@ -46,7 +47,7 @@ class ItemService extends Service
                 ->update(['del_time' => $time]);
             $this->saveDetail($model->id, $time, $data);
 
-            list($status, $msg) = CustomFieldSettingService::syncCustomFieldData($model->id,$data,$user);
+            list($status, $msg) = CustomFieldSettingService::syncCustomFieldData($model->id, $tableName, $data, $user);
             if (! $status) {
                 DB::rollBack();
                 return [false, $msg];
@@ -69,6 +70,7 @@ class ItemService extends Service
             DB::beginTransaction();
 
             $model = new Item();
+            $tableName = $model->getTable();
             $model->code = $data['code'] ?? '';
             $model->title = $data['title'] ?? '';
             $model->mark = $data['mark'] ?? "";
@@ -88,7 +90,7 @@ class ItemService extends Service
 
             $this->saveDetail($model->id, time(), $data);
 
-            list($status, $msg) = CustomFieldSettingService::syncCustomFieldData($model->id,$data,$user);
+            list($status, $msg) = CustomFieldSettingService::syncCustomFieldData($model->id, $tableName, $data, $user);
             if (!$status) {
                 DB::rollBack();
                 return [false, $msg];
@@ -216,15 +218,13 @@ class ItemService extends Service
     public function itemDetail($data, $user){
         if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
         $customer = Item::where('del_time',0)
+            ->withCustomData($user)
             ->where('id',$data['id'])
-            ->withCustomData($data['menu_id'] ?? 0, $user) //预加载表头自定义项数据
             ->first();
         if(empty($customer)) return [false,'项目不存在或已被删除'];
+        $customer->getFormattedCustomFields();// 自定义数据附件处理
         $customer = $customer->toArray();
 
-        //填充自定义表头附件地址
-        (new CustomFieldSettingService())->fillDataCustomField($customer);
-
         $customer['start_time']  = ! empty($customer['start_time']) ? date("Y-m-d", $customer['start_time']) : "";
         $customer['end_time']  = ! empty($customer['end_time']) ? date("Y-m-d", $customer['end_time']) : "";
         $customer['crt_name'] = Employee::where('id',$customer['crt_id'])->value('title');
@@ -244,7 +244,7 @@ class ItemService extends Service
 
         $model = Item::TopClear($user,$data);
         $model = $model->where('del_time',0)
-            ->withCustomData($data['menu_id'] ?? 0, $user) //预加载表头自定义项数据
+//            ->withCustomData($user)
             ->select($field)
             ->orderby('id', 'desc');
 

+ 23 - 42
app/Traits/HasCustomFields.php

@@ -3,64 +3,45 @@
 namespace App\Traits;
 
 use App\Model\CustomFieldValue;
+use App\Model\SysOssTasks;
+use App\Service\FileUploadService;
 
 trait HasCustomFields
 {
     /**
-     * 基础关联:获取该模型实例关联的所有自定义值
+     * 基础关联:关联到新的自定义值表
+     * 自动通过当前模型的 getTable() 获取 table_name 过滤
      */
-    public function customFieldValues()
+    public function customFields()
     {
-        return $this->hasMany(CustomFieldValue::class, 'model_id')->where('del_time', 0);
+        return $this->hasMany(CustomFieldValue::class, 'model_id')
+            ->where('table_name', $this->getTable())
+            ->where('del_time', 0)
+            ->select('id','field_label','field_value','field_type','model_id');
     }
 
     /**
-     * 获取指定菜单下的自定义字段数据
+     * 获取格式化后的自定义字段列表
+     * 用于详情页接口返回给前端 custom_fields 数组
      */
-    public function getCustomFieldsByMenu($menuId)
+    public function getFormattedCustomFields()
     {
-        // 预加载已经在 itemCommon 中完成,这里 get() 会直接使用缓存数据
-        return $this->customFieldValues
-            ->filter(function ($item) use ($menuId) {
-                // 过滤属于该菜单的定义(因为 with 预加载了 definition)
-                return $item->definition && $item->definition->menu_id == $menuId;
-            })
-            ->map(function ($item) {
-                $definition = $item->definition;
-                $value = $item->field_value;
+        return $this->customFields->map(function (&$item) {
+            $field_value_show = "";
+            if($item->field_type == SysOssTasks::type_two) $field_value_show = $this->getCustomFileUrl($item->field_value);
+            $item['field_value_show'] = $field_value_show;
 
-                $url = null;
-                // 1. 处理文件 URL 逻辑
-                if ($definition->field_type === 'file' && $value) {
-                    // 调用之前写的 getFileShow,并传入当前模型的 top_depart_id
-//                    $url = (new FileUploadService())->getFileShow($value, $this->top_depart_id);
-                    $url = "";
-                }
-
-                return [
-                    'definition_id' => $definition->id,
-                    'key'           => $definition->field_key,
-                    'label'         => $definition->field_label,
-                    'type'          => $definition->field_type,
-                    'value'         => $value,
-                    'url'           => $url, // 这一步是前端展示的关键
-                ];
-            })
-            ->values(); // 重置集合索引
+            return $item;
+        })->values()->toArray();
     }
 
     /**
-     * 辅助方法:快速获取某个特定字段的值
+     * 内部方法:处理附件显示地址
      */
-    public function getCustomFieldValueByKey($menuId, $fieldKey)
+    protected function getCustomFileUrl($path)
     {
-        $item = $this->customFieldValues
-            ->first(function ($val) use ($menuId, $fieldKey) {
-                return $val->definition
-                    && $val->definition->menu_id == $menuId
-                    && $val->definition->field_key == $fieldKey;
-            });
-
-        return $item ? $item->field_value : null;
+        if (empty($path)) return "";
+        $fileUploadService = new FileUploadService();
+        return $fileUploadService->getFileShow($path);
     }
 }