cqp 4 hafta önce
ebeveyn
işleme
3fda8b8d41

+ 26 - 0
app/Http/Controllers/Api/WorkFlowController.php

@@ -73,4 +73,30 @@ class WorkFlowController extends BaseController
             return $this->json_return(201,$data);
         }
     }
+
+    public function getMyPendingApprovals(Request $request)
+    {
+        $service = new WorkFlowService();
+        $user = $request->userData;
+        list($status,$data) = $service->getMyPendingApprovals($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function approval(Request $request)
+    {
+        $service = new WorkFlowService();
+        $user = $request->userData;
+        list($status,$data) = $service->approval($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
 }

+ 23 - 0
app/Model/Draft.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Model;
+
+use Illuminate\Database\Eloquent\Model;
+
+//草稿
+class Draft extends DataScopeBaseModel
+{
+    protected $guarded = [];
+    protected $table = "draft"; //指定表
+    const CREATED_AT = 'crt_time';
+    const UPDATED_AT = 'upd_time';
+    protected $dateFormat = 'U';
+    public static $field = ['id', 'type', 'document_id', 'crt_id', 'crt_time', 'upd_time'];
+
+    /**
+     * 属性转换为原生类型
+     */
+    protected $casts = [
+        'content' => 'array', // 声明 content 字段读取时自动转为数组
+    ];
+}

+ 16 - 0
app/Model/WorkFlowInstances.php

@@ -13,4 +13,20 @@ class WorkFlowInstances extends DataScopeBaseModel
     const UPDATED_AT = 'upd_time';
     protected $dateFormat = 'U';
     public static $field = ['id', 'template_id', 'document_id', 'document_type', 'status', 'crt_id', 'crt_time', 'upd_time'];
+
+    const status_one = 'pending';
+    const status_two = 'processing';
+    const status_three = 'approved';
+    const status_four = 'rejected';
+
+    /**
+     * 定义与模板表的关联
+     */
+    public function template()
+    {
+        // 参数1:关联的模型类名
+        // 参数2:当前表(instances)中用来关联的字段名
+        // 参数3:目标表(templates)的主键名
+        return $this->belongsTo(WorkFlowTemplates::class, 'template_id', 'id');
+    }
 }

+ 4 - 0
app/Model/WorkFlowInstancesNodes.php

@@ -13,4 +13,8 @@ class WorkFlowInstancesNodes extends DataScopeBaseModel
     const UPDATED_AT = 'upd_time';
     protected $dateFormat = 'U';
     public static $field = ['id', 'instance_id', 'node_key', 'label', 'prev_node_key', 'next_node_key', 'assignees', 'approval_type', 'status', 'handled_time', 'crt_time', 'upd_time', 'crt_id'];
+
+    protected $casts = [
+        'assignees' => 'array',
+    ];
 }

+ 7 - 0
app/Model/WorkFlowTemplates.php

@@ -13,4 +13,11 @@ class WorkFlowTemplates extends DataScopeBaseModel
     const UPDATED_AT = 'upd_time';
     protected $dateFormat = 'U';
     public static $field = ['id', 'code', 'title', 'crt_id', 'crt_time', 'upd_time'];
+
+    /**
+     * 属性转换为原生类型
+     */
+    protected $casts = [
+        'content' => 'array', // 声明 content 字段读取时自动转为数组
+    ];
 }

+ 131 - 15
app/Service/ItemService.php

@@ -5,6 +5,7 @@ namespace App\Service;
 use App\Jobs\ProcessOssTask;
 use App\Model\CustomFieldValue;
 use App\Model\Device;
+use App\Model\Draft;
 use App\Model\Employee;
 use App\Model\Item;
 use App\Model\ItemDetails;
@@ -20,6 +21,7 @@ use App\Model\ItemNodeMissionEmployee;
 use App\Model\SysOssTasks;
 use App\Model\Tag;
 use App\Model\SysMenu;
+use App\Model\WorkFlowTemplates;
 use Illuminate\Support\Facades\DB;
 
 class ItemService extends Service
@@ -218,14 +220,32 @@ class ItemService extends Service
     public function itemEdit($data,$user){
         list($status,$msg) = $this->itemRule($data, $user, false);
         if(!$status) return [$status,$msg];
+        $origin_data = $msg;
 
         try {
             DB::beginTransaction();
 
             $model = Item::where('id', $data['id'])->first();
-            $old_employee_id = $model->charge_id;
-            $crt_id = $model->crt_id;
             $tableName = $model->getTable();
+
+            if(isset($data['draft'])){
+                // 创建草稿
+                $this->createDraft($origin_data, $user, $tableName);
+                // 更新项目状态
+                $model->state = Item::TYPE_MINUS_ONE;
+                $model->save();
+                //触发审批
+                list($status, $msg) = (new WorkFlowService())->triggerWorkflow($data['review_id'], $model->id,$tableName,$user);
+                if(! $status) {
+                    DB::rollBack();
+                    return [false, $msg];
+                }
+
+                DB::commit();
+                return [true, '更新记录成功,记录草稿,进入审批流程'];
+            }
+
+            $old_employee_id = $model->charge_id;
             $model->code = $data['code'] ?? '';
             $model->title = $data['title'] ?? '';
             $model->mark = $data['mark'] ?? "";
@@ -236,14 +256,12 @@ class ItemService extends Service
             $model->charge_id = $data['charge_id'] ?? 0;
             $model->field = $data['field'] ?? "";
             $model->item_attribute = $data['item_attribute'] ?? 0;
-
             //项目管理的字段
             if($user['select_tree_type'] == SysMenu::tree_type_one){
                 $model->is_review_required = $data['is_review_required'] ?? 0;
                 $model->review_id = $data['review_id'] ?? 0;
                 $model->priority_id = $data['priority_id'] ?? 0;
             }
-
             $model->save();
 
             $time = time();
@@ -256,21 +274,13 @@ class ItemService extends Service
                 ->update(['del_time' => $time]);
             $this->saveDetail($model->id, $time, $data);
 
+            //客户自定义字段
             list($status, $msg) = CustomFieldSettingService::syncCustomFieldData($model->id, $tableName, $data, $user);
             if (! $status) {
                 DB::rollBack();
                 return [false, $msg];
             }
 
-            //如果编辑人不是创建人触发审核
-            if($user['id'] != $crt_id){
-                list($status, $msg) = (new WorkFlowService())->triggerWorkflow($data['review_id'], $model->id,$tableName,$user);
-                if(! $status) {
-                    DB::rollBack();
-                    return [false, $msg];
-                }
-            }
-
             DB::commit();
         }catch (\Exception $exception){
             DB::rollBack();
@@ -327,6 +337,90 @@ class ItemService extends Service
         return [true, ''];
     }
 
+    public function checkIsChanged($data, $user) {
+        // 1. 获取旧数据
+        list($status, $oldData) = $this->itemDetail($data, $user);
+        if (!$status) return [false, $oldData];
+
+        //工时创建 直接通过
+        $select_tree_type = $user['select_tree_type'];
+        if(empty($select_tree_type)) return false;
+
+        //创建人 直接通过
+        if($user['id'] == $oldData['crt_id']) return false;
+
+        // --- 表头基础字段比对 ---
+        $fields = [
+            'code', 'title', 'mark', 'state', 'budget',
+            'charge_id', 'item_attribute', 'start_time', 'end_time'
+        ];
+
+        // 权限字段校验
+        if (($user['select_tree_type']) == SysMenu::tree_type_one) {
+            $fields = array_merge($fields, ['is_review_required', 'review_id', 'priority_id']);
+        }
+
+        foreach ($fields as $field) {
+            $oldValue = $oldData[$field] ?? '';
+            $newValue = $data[$field] ?? '';
+
+            // 日期格式对齐(确保 Y-m-d 格式一致)
+            if (in_array($field, ['start_time', 'end_time'])) {
+                $newValue = !empty($newValue) ? date("Y-m-d", $newValue) : "";
+            }
+
+            if ($oldValue != $newValue) {
+                return true;
+            }
+        }
+
+        // --- 自定义字段比对 ---
+        if (isset($data['custom_fields']) && is_array($data['custom_fields'])) {
+            $oldCustomMap = array_column($oldData['custom_fields'] ?? [], 'field_value', 'id');
+
+            foreach ($data['custom_fields'] as $customField) {
+                $fid = $customField['id'] ?? 0;
+                $newVal = $customField['field_value'] ?? '';
+                $oldVal = $oldCustomMap[$fid] ?? '';
+
+                if ($oldVal != $newVal) {
+                    return true;
+                }
+            }
+        }
+
+        // --- 表体:人员名单比对 (man_list) ---
+        $oldMans = collect($oldData['man_list'] ?? [])->pluck('data_id')->sort()->values()->toArray();
+        $newMans = collect($data['man_list'] ?? [])->pluck('data_id')->sort()->values()->toArray();
+        if ($oldMans !== $newMans) {
+            return true;
+        }
+
+        // --- 表体:设备名单比对 (device_list) ---
+        $oldDevices = collect($oldData['device_list'] ?? [])->pluck('data_id')->sort()->values()->toArray();
+        $newDevices = collect($data['device_list'] ?? [])->pluck('data_id')->sort()->values()->toArray();
+        if ($oldDevices !== $newDevices) {
+            return true;
+        }
+
+        return false; // 没有任何变动
+    }
+
+    public function createDraft($data, $user, $type, $opt_type = 1) {
+        Draft::insert([
+            'document_type' => $type,
+            'document_id' => $data['id'],
+            'content' => json_encode($data),
+            'opt_user' => json_encode($user),
+            'crt_id' => $user['id'],
+            'top_depart_id' => $user['top_depart_id'],
+            'opt_type' => $opt_type,
+            'crt_time' => time()
+        ]);
+
+        return true;
+    }
+
     private function saveDetail($id, $time, $data){
         if(! empty($data['man_list'])){
             $unit = [];
@@ -529,6 +623,7 @@ class ItemService extends Service
         $customer['crt_time'] = $customer['crt_time'] ? date("Y-m-d H:i:s",$customer['crt_time']): '';
         $customer['state_title'] = Item::State_Type[$customer['state']] ?? '';
         $customer['priority_title'] = Tag::where('id', $customer['priority_id'])->value('title') ?? "";
+        $customer['review_title'] = WorkFlowTemplates::where('id', $customer['review_id'])->value('title') ?? "";
 
         $details = $this->getDetail($data['id']);
         $customer = array_merge($customer, $details);
@@ -692,6 +787,7 @@ class ItemService extends Service
     }
 
     public function itemRule(&$data, $user, $is_add = true){
+        $origin_data = $data;
         $data['top_depart_id'] = $user['top_depart_id'];
         if(empty($data['code'])) return [false, '项目编码不能为空'];
         if(empty($data['title'])) return [false, '项目名称不能为空'];
@@ -734,15 +830,33 @@ class ItemService extends Service
                 ->exists();
         }else{
             if(empty($data['id'])) return [false,'ID不能为空'];
+            $item = Item::where('id', $data['id'])
+                ->where('del_time',0)
+                ->first();
+            if(empty($item)) return [false, '项目不存在或已被删除'];
+
             $bool = Item::where('code',$data['code'])
                 ->where('top_depart_id', $data['top_depart_id'])
-                ->where('id','<>',$data['id'])
+                ->where('id','<>', $data['id'])
                 ->where('del_time',0)
                 ->exists();
+
+            if($user['select_tree_type'] && $item->state == Item::TYPE_MINUS_ONE) return [false, '审核中不允许编辑'];
         }
         if($bool) return [false, '项目编码已存在'];
 
-        return [true, ''];
+        //判断是否编辑 然后需要审核的 需要后 就更新状态 记录草稿
+        if(! $is_add){
+            $return = $this->checkIsChanged($data, $user);
+            if(is_array($return)) {
+                list($status, $msg) = $return;
+                return [$status, $msg];
+            }else{
+                if($item->review_id && $return) $data['draft'] = true;
+            }
+        }
+
+        return [true, $origin_data];
     }
 
     public function fillData($data){
@@ -1142,6 +1256,7 @@ class ItemService extends Service
         $item = Item::where('id', $customer['item_id'])->first();
         $customer['item_code'] = $item->code;
         $customer['item_title'] = $item->title;
+        $customer['review_title'] = WorkFlowTemplates::where('id', $customer['review_id'])->value('title') ?? "";
 
         $details = $this->getNodeDetail($data['id']);
         $customer = array_merge($customer, $details);
@@ -1647,6 +1762,7 @@ class ItemService extends Service
         $customer['mission_title'] = $node_tmp['title'] ?? '';
         $customer['mission_code'] = $node_tmp['code'] ?? '';
         $customer['parent_item_node_mission_title'] = ItemNodeMission::where('id', $customer['parent_item_node_mission_id'])->value('title') ?? '';
+        $customer['review_title'] = WorkFlowTemplates::where('id', $customer['review_id'])->value('title') ?? "";
 
         $item_node_map = $this->getItemNodeMap($customer['id']);
         $customer['item_node_title'] = $item_node_map[$customer['id']];

+ 161 - 29
app/Service/WorkFlowService.php

@@ -184,7 +184,7 @@ class WorkFlowService extends Service
             'template_id'   => $template->id,
             'document_id'   => $documentId,
             'document_type' => $documentType,
-            'status'        => 'processing',
+            'status'        => WorkFlowInstances::status_two, // 激活
             'crt_id'        => $user['id'],
             'crt_time'      => time(),
             'upd_time'      => time(),
@@ -205,6 +205,13 @@ class WorkFlowService extends Service
             $prev = collect($edges)->where('target', $nodeKey)->pluck('source')->first();
             $next = collect($edges)->where('source', $nodeKey)->pluck('target')->first();
 
+            $tempAssignees = $node['data']['assignees'] ?? [];
+            foreach($tempAssignees as $k => $person) {
+                $tempAssignees[$k]['is_pass'] = 0; // 0-未审,1-通过,2-驳回
+                $tempAssignees[$k]['mark'] = '';
+                $tempAssignees[$k]['pass_time'] = 0;
+            }
+
             $insertNodes[] = [
                 'instance_id'   => $instance->id,
                 'node_key'      => $nodeKey,
@@ -213,7 +220,7 @@ class WorkFlowService extends Service
                 'next_node_key' => $next, // 下级节点 ID
 
                 // 审批人快照存储 [cite: 1]
-                'assignees'     => json_encode($node['data']['assignees'] ?? []),
+                'assignees'     => json_encode($tempAssignees),
 
                 // 审批类型:1 会签,2 或签 [cite: 1]
                 'approval_type' => $node['data']['type'] ?? 2,
@@ -238,44 +245,169 @@ class WorkFlowService extends Service
     /**
      * 获取当前用户的待办审核列表
      */
-    public function getMyPendingApprovals($userId, $topDepartId)
+    public function getMyPendingApprovals($data, $user)
     {
-        // 1. 在节点实例表中查询
-        // 条件:公司隔离 + 状态为审批中(1) + 审批人JSON中包含当前用户ID
-        $pendingNodes = WorkFlowInstancesNodes::where('top_depart_id', $topDepartId)
+        $userId = $user['id'];
+        $topDepartId = $user['top_depart_id'];
+
+        // 1. 构造查询对象
+        $model = WorkFlowInstancesNodes::where('top_depart_id', $topDepartId)
             ->where('status', 1) // 1 = 审批中
-            ->whereRaw('JSON_CONTAINS(assignees, JSON_OBJECT("id", ?))', [$userId])
-            ->get();
+            ->whereRaw('JSON_CONTAINS(assignees, JSON_OBJECT("id", ?))', [(int)$userId])
+            // 如果我是“或签”,只要我还没审,节点状态就是1,我能看到;
+            // 如果我是“会签”,我审过了但别人没审,节点状态还是1,但通过这个排除条件,我看不到了。
+            ->whereRaw('NOT JSON_CONTAINS(assignees, JSON_OBJECT("id", ?, "is_pass", 1))', [$userId])
+            ->orderBy('id', 'desc');
+        $pageData = $this->limit($model, '', $data);
 
-        if ($pendingNodes->isEmpty()) {
-            return [];
-        }
+        if (empty($pageData['data'])) return [true, $pageData];
 
-        // 2. 获取关联的实例信息和业务单据信息
-        // 建议在 Model 中定义好 belongsTo 关系
-        $instanceIds = $pendingNodes->pluck('instance_id')->unique();
+        // 3. 提取当前页的所有 instance_id
+        $instanceIds = array_unique(array_column($pageData['data'], 'instance_id'));
 
-        $instances = WorkFlowInstances::with(['template']) // 关联查出模板名称等信息
-        ->whereIn('id', $instanceIds)
+        // 4. 获取关联信息
+        $instances = WorkFlowInstances::with(['template'])
+            ->whereIn('id', $instanceIds)
             ->get()
             ->keyBy('id');
 
-        // 3. 组装返回数据
-        $result = $pendingNodes->map(function ($node) use ($instances) {
-            $instance = $instances[$node->instance_id] ?? null;
+        // 5. 组装返回数据 (遍历当前页的数据进行填充)
+        foreach ($pageData['data'] as $key => $node) {
+            $instance = $instances[$node['instance_id']] ?? null;
 
-            return [
-                'node_instance_id' => $node->id,        // 节点实例ID(审批接口要用)
-                'instance_id'      => $node->instance_id, // 流程实例ID
-                'node_label'       => $node->label,       // 当前环节名称(如:财务审批)
-                'document_id'      => $instance->document_id ?? 0,   // 业务单据ID
-                'document_type'    => $instance->document_type ?? '', // 业务模
+            $pageData['data'][$key] = [
+                'node_instance_id' => $node['id'],
+                'instance_id'      => $node['instance_id'],
+                'node_label'       => $node['label'],
+                'document_id'      => $instance->document_id ?? 0, //业务单据id
+                'document_type'    => $instance->document_type ?? '', //业务单据类
                 'flow_title'       => $instance->template->title ?? '未知流程',
-                'apply_time'       => $instance->crt_time ?? 0,       // 发起时间
-                'approval_type'    => $node->approval_type,           // 1会签/2或签
+                'apply_time_fmt'   => isset($instance->crt_time->timestamp) ? date('Y-m-d H:i:s', $instance->crt_time->timestamp) : '',
+                'approval_type'    => $node['approval_type'],
             ];
-        });
+        }
+
+        return [true, $pageData];
+    }
+
+    public function approval($data, $user){
+        list($status, $msg, $result) = $this->approve($data, $user);
+        if(! $status) return [false, $msg];
+
+        $instance_id = $msg;
+        if($result == 1){
+
+        }elseif ($result == 2){
+
+        }
+
+        return [true, ''];
+    }
+
+    /**
+     * @param int $nodeInstanceId 节点实例ID
+     * @param int $status 结果:2-通过,3-驳回
+     * @param string $remark 意见
+     * @param array $user 当前操作人
+     *
+     * 业务结果说明:0-审批中(继续等待), 1-最终通过(业务单据可改为已完成), 2-已驳回(业务单据可改为已拒绝)
+     */
+    public function approve($data, $user)
+    {
+        $nodeInstanceId = $data['node_instance_id'] ?? 0;
+        $status = $data['status'] ?? 2;
+        $remark = $data['mark'] ?? '';
+
+        $currentNode = WorkFlowInstancesNodes::where('id', $nodeInstanceId)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->first();
+
+        if (!$currentNode || $currentNode->status != 1) return [false, '环节无效或已处理', 0];
 
-        return $result;
+        $assignees = is_string($currentNode->assignees) ? json_decode($currentNode->assignees, true) : $currentNode->assignees;
+
+        $myIndex = collect($assignees)->search(fn($item) => $item['id'] == $user['id']);
+        if ($myIndex === false) return [false, '您不在审批人名单中', 0];
+
+        try {
+            DB::beginTransaction();
+
+            $time = time();
+            $assignees[$myIndex]['mark'] = $remark;
+            $assignees[$myIndex]['pass_time'] = $time;
+
+            // --- 情况 A:驳回 ---
+            if ($status == 3) {
+                $currentNode->status = 3;
+                $currentNode->handled_time = $time;
+                $assignees[$myIndex]['is_pass'] = 2;
+                $currentNode->assignees = $assignees;
+                $currentNode->save();
+
+                WorkFlowInstances::where('id', $currentNode->instance_id)->update(['status' => WorkFlowInstances::status_four]);
+
+                DB::commit();
+                return [true, $currentNode->instance_id, 2];
+            }
+
+            // --- 情况 B:通过 ---
+            $assignees[$myIndex]['is_pass'] = 1;
+            $currentNode->assignees = $assignees;
+
+            $shouldMoveToNext = false;
+            if ($currentNode->approval_type == 2) {
+                $shouldMoveToNext = true;
+            } else {
+                $unPassedCount = collect($assignees)->where('is_pass', '!=', 1)->count();
+                if ($unPassedCount == 0) {
+                    $shouldMoveToNext = true;
+                }
+            }
+
+            $finalResult = 0; // 默认:还在审批中
+
+            if ($shouldMoveToNext) {
+                $currentNode->status = 2;
+                $currentNode->handled_time = $time;
+                $currentNode->save();
+
+                if (!empty($currentNode->next_node_key)) {
+                    $hasActiveNext = $this->activateNextNode($currentNode->instance_id, $currentNode->next_node_key);
+                    if (!$hasActiveNext) {
+                        // 如果逻辑上虽然有key但没找到下个节点,也视为终结
+                        WorkFlowInstances::where('id', $currentNode->instance_id)->update(['status' => WorkFlowInstances::status_three]);
+                        $finalResult = 1;
+                    }
+                } else {
+                    // 没有下个节点,流程最终终结
+                    WorkFlowInstances::where('id', $currentNode->instance_id)->update(['status' => WorkFlowInstances::status_three]);
+                    $finalResult = 1; // 返回 1,表示业务层面可以执行“生效”动作
+                }
+            } else {
+                // 会签中,还有人没审
+                $currentNode->save();
+                $finalResult = 0;
+            }
+
+            DB::commit();
+            return [true, $currentNode->instance_id, $finalResult];
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return [false, $e->getMessage(), 0];
+        }
+    }
+
+    private function activateNextNode($instanceId, $nextNodeKey)
+    {
+        $nextNode = WorkFlowInstancesNodes::where('instance_id', $instanceId)
+            ->where('node_key', $nextNodeKey)
+            ->first();
+
+        if ($nextNode) {
+            $nextNode->status = 1;
+            $nextNode->save();
+            return true;
+        }
+        return false;
     }
 }

+ 5 - 0
routes/api.php

@@ -146,6 +146,11 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     $route->any('workFlowDel', 'Api\WorkFlowController@workFlowDel');
     $route->any('workFlowDetail', 'Api\WorkFlowController@workFlowDetail');
 
+    //待我审核
+    $route->any('getMyPendingApprovals', 'Api\WorkFlowController@getMyPendingApprovals');
+    //审核接口
+    $route->any('approval', 'Api\WorkFlowController@approval');
+
     //费用类型
     $route->any('feeAdd', 'Api\FeeController@feeAdd')->name('fee.add');
     $route->any('feeEdit', 'Api\FeeController@feeEdit')->name('fee.edit');