cqp 1 day ago
parent
commit
48cf431c2b

+ 13 - 0
app/Http/Controllers/Api/BIController.php

@@ -20,6 +20,19 @@ class BIController extends BaseController
         }
     }
 
+    public function homePageItemData(Request $request)
+    {
+        $service = new BIService();
+        $user = $request->userData;
+        list($status,$data) = $service->homePageItemData($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
     public function cockpit(Request $request)
     {
         $service = new BIService();

+ 105 - 0
app/Http/Controllers/Api/ItemController.php

@@ -402,4 +402,109 @@ class ItemController extends BaseController
             return $this->json_return(201,$data);
         }
     }
+
+    public function shareAdd(Request $request)
+    {
+        $service = new ItemService();
+        $user = $request->userData;
+        list($status,$data) = $service->shareAdd($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function shareEdit(Request $request)
+    {
+        $service = new ItemService();
+        $user = $request->userData;
+        list($status,$data) = $service->shareEdit($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function shareList(Request $request)
+    {
+        $service = new ItemService();
+        $user = $request->userData;
+        list($status,$data) = $service->shareList($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function shareDetail(Request $request)
+    {
+        $service = new ItemService();
+        $user = $request->userData;
+        list($status,$data) = $service->shareDetail($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function shareCancel(Request $request)
+    {
+        $service = new ItemService();
+        $user = $request->userData;
+        list($status,$data) = $service->shareCancel($request->all(),$user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function shareOutSideHTML(Request $request)
+    {
+        $service = new ItemService();
+        list($status,$data) = $service->shareOutSideHTML($request->all());
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function itemMissionProgressBoard(Request $request)
+    {
+        $service = new ItemService();
+        $user = $request->userData;
+        $re = $request->all();
+        $re['is_my'] = 1;
+        list($status,$data) = $service->itemMissionProgressBoard($re, $user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function itemNodeMissionUpdateProgressContentOutSide(Request $request)
+    {
+        $service = new ItemService();
+        $user = $request->userData;
+        list($status,$data) = $service->itemNodeMissionUpdateProgressContentOutSide($request->all(), $user);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
 }

+ 31 - 0
app/Model/ItemNodeMissionShare.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Model;
+
+class ItemNodeMissionShare extends DataScopeBaseModel
+{
+    protected $table = "item_node_mission_share"; //指定表
+    const CREATED_AT = 'crt_time';
+    const UPDATED_AT = 'upd_time';
+    protected $dateFormat = 'U';
+    const employee_column = "crt_id";
+    const Order_type = "item_node_mission_share";
+
+    public static $field = ['*'];
+
+    const TYPE_ZERO = 0;
+    const TYPE_ONE = 1;
+    const TYPE_TWO = 2;
+    const State_Type = [
+        self::TYPE_ZERO => '生效中',
+        self::TYPE_ONE => '已过期',
+        self::TYPE_TWO => '已作废',
+    ];
+
+    const QX_TYPE_ZERO = 0;
+    const QX_TYPE_ONE = 1;
+    const QX_Type = [
+        self::QX_TYPE_ZERO => '填报进展',
+        self::QX_TYPE_ONE => '仅查看',
+    ];
+}

+ 12 - 0
app/Service/BIService.php

@@ -533,4 +533,16 @@ class BIService extends Service
             'fee_total_num' => $totalEmployees,
         ];
     }
+
+    public function homePageItemData($data, $user){
+        // 项目相关查询
+        $item = $this->ItemAboutSearch($data, $user);
+    }
+
+    private function ItemAboutSearch($data, $user){
+        $model = Item::TopClear($user,$data);
+        $model = $model->where('del_time',0)
+            ->select('title','start_time','end_time','state','charge_id','')
+            ->orderby('id', 'desc');
+    }
 }

+ 413 - 194
app/Service/ItemService.php

@@ -18,6 +18,7 @@ use App\Model\ItemNodeMission;
 use App\Model\ItemNodeMissionContent;
 use App\Model\ItemNodeMissionDetails;
 use App\Model\ItemNodeMissionEmployee;
+use App\Model\ItemNodeMissionShare;
 use App\Model\SysOssTasks;
 use App\Model\Tag;
 use App\Model\SysMenu;
@@ -2088,102 +2089,6 @@ class ItemService extends Service
         return [true, $list];
     }
 
-    public function itemNodeRule1(&$data, $user, $is_add = true){
-        $data['top_depart_id'] = $user['top_depart_id'];
-        if(empty($data['item_id'])) return [false, '项目ID不能为空'];
-        $item = Item::where('id', $data['item_id'])->where('del_time',0)->first();
-        if(empty($item)) return [false, '项目不存在或已被删除'];
-        $item = $item->toArray();
-        if($item['state'] == Item::TYPE_THREE) return [false, '项目已完结,操作失败'];
-        if(empty($data['title'])) return [false, '节点名称不能为空'];
-        if(! isset($data['node_weight'])) return [false, '节点权重不存在'];
-        list($status, $res) = $this->checkWeight($data['node_weight'], '节点权重');
-        if(! $status) return [false, $res];
-        if(! empty($data['start_time'])) $data['start_time'] = $this->changeDateToDate($data['start_time']);
-        if(! empty($data['end_time'])) $data['end_time'] = $this->changeDateToDate($data['end_time'],true);
-        if(! empty($data['start_time']) && ! empty($data['end_time']) && $data['start_time'] > $data['end_time']) return [false, '开始时间不能大于结束时间'];
-
-        $itemStartTime = $item['start_time'];
-        $itemEndTime = $item['end_time'];
-        if (!empty($data['start_time'])) {
-            $inputStartTime = $data['start_time'];
-
-            if ($itemStartTime > 0 && $inputStartTime < $itemStartTime) {
-                return [false, '节点开始时间不能早于项目开始时间:' . date('Y-m-d', $itemStartTime)];
-            }
-        }
-        if (!empty($data['end_time'])) {
-            $inputEndTime = $data['end_time'];
-            if ($itemEndTime > 0 && $inputEndTime > $itemEndTime) {
-                return [false, '节点结束时间不能晚于项目结束时间:' . date('Y-m-d', $itemEndTime)];
-            }
-        }
-
-        if(! empty($data['man_list'])) {
-            foreach ($data['man_list'] as $key => $value){
-                if(empty($value['type'])) return [false, '类型不能为空'];
-                if(empty($value['data_id'])) return [false, '人员不能为空'];
-                $data['man_list'][$key]['top_depart_id'] = $data['top_depart_id'];
-            }
-            list($status, $msg) = $this->checkArrayRepeat($data['man_list'],'data_id','人员');
-            if(! $status) return [false, $msg];
-        }
-
-        if(! empty($data['device_list'])) {
-            foreach ($data['device_list'] as $key => $value){
-                if(empty($value['type'])) return [false, '类型不能为空'];
-                if(empty($value['data_id'])) return [false, '设备ID不能为空'];
-                $data['device_list'][$key]['top_depart_id'] = $data['top_depart_id'];
-            }
-            list($status, $msg) = $this->checkArrayRepeat($data['device_list'],'data_id','设备');
-            if(! $status) return [false, $msg];
-        }
-
-        if($is_add){
-            $bool = ItemNode::where('item_id',$data['item_id'])
-                ->where('top_depart_id', $data['top_depart_id'])
-                ->where('title', $data['title'])
-                ->where('del_time',0)
-                ->exists();
-        }else{
-            if(empty($data['id'])) return [false,'ID不能为空'];
-            $item = ItemNode::where('id', $data['id'])
-                ->where('del_time',0)
-                ->first();
-            if(empty($item)) return [false, '项目节点不存在或已被删除'];
-            if($item->approval_state == ItemNode::TYPE_MINUS_ONE) return [false, '节点审核中,操作失败'];
-
-            if($data['state'] == ItemNode::TYPE_THREE) {
-                $bool = ItemNodeMission::where('del_time',0)
-                    ->where('item_node_id', $data['id'])
-                    ->where('state', '<>',ItemNodeMission::TYPE_THREE)
-                    ->exists();
-                if($bool) return [false, '节点下存在任务未完结'];
-            }
-
-            $bool = ItemNode::where('title', $data['title'])
-                ->where('item_id',$data['item_id'])
-                ->where('top_depart_id', $data['top_depart_id'])
-                ->where('id','<>',$data['id'])
-                ->where('del_time',0)
-                ->exists();
-        }
-        if($bool) return [false, '该项目下节点名称已存在'];
-
-        //判断是否编辑 然后需要审核的 需要后 就更新状态 记录草稿
-        if(! $is_add){
-            $return = $this->checkItemNodeIsChanged($data, $user);
-            if(is_array($return)) {
-                list($status, $msg) = $return;
-                return [$status, $msg];
-            }else{
-                if($return) $data['draft'] = true;
-            }
-        }
-
-        return [true, ''];
-    }
-
     public function itemNodeRule(&$data, $user, $is_add = true)
     {
         $data['top_depart_id'] = $user['top_depart_id'];
@@ -2826,8 +2731,13 @@ class ItemService extends Service
     }
 
     public function itemMissionProgressBoard($data,$user){
+        $is_my = $data['is_my'] ?? 0;
+        $my = $user['id'] ?? 0;
         $list = ItemNodeMissionContent::where('del_time',0)
             ->where('item_node_mission_id', $data['id'])
+            ->when(! empty($is_my),function ($query) use($my){
+                return $query->where('crt_id', $my);
+            })
             ->select('*')
             ->get()->toArray();
 
@@ -2990,104 +2900,6 @@ class ItemService extends Service
         return [true, $model];
     }
 
-    public function itemNodeMissionRule1(&$data, $user, $is_add = true){
-        $data['top_depart_id'] = $user['top_depart_id'];
-        if(empty($data['item_node_id']) && empty($data['parent_item_node_mission_id'])) return [false, '节点与父级任务不能同时为空'];
-        if(empty($data['item_node_id'])) return [false, '项目节点ID不能为空'];
-        $item = ItemNode::where('id', $data['item_node_id'])->where('del_time',0)->first();
-        if(empty($item)) return [false, '项目节点不存在或已被删除'];
-        $item = $item->toArray();
-        if($item['state'] == ItemNode::TYPE_THREE) return [false, '节点已完结,操作失败'];
-        $data['item_id'] = $item['item_id'];
-        if(! empty($data['parent_item_node_mission_id'])) {
-            $bool = ItemNodeMission::where('id', $data['parent_item_node_mission_id'])->where('del_time',0)->exists();
-            if(! $bool) return [false, '父任务不存在或已被删除'];
-        }
-        if(empty($data['title'])) return [false, '任务名称不能为空'];
-        if(! isset($data['mission_weight'])) return [false, '任务权重不存在'];
-        list($status, $res) = $this->checkWeight($data['mission_weight'], '任务权重');
-        if(! $status) return [false, $res];
-        if(! empty($data['start_time'])) $data['start_time'] = $this->changeDateToDate($data['start_time']);
-        if(! empty($data['end_time'])) $data['end_time'] = $this->changeDateToDate($data['end_time'],true);
-        if(! empty($data['start_time']) && ! empty($data['end_time']) && $data['start_time'] > $data['end_time']) return [false, '开始时间不能大于结束时间'];
-
-        $itemStartTime = $item['start_time'];
-        $itemEndTime = $item['end_time'];
-        if (!empty($data['start_time'])) {
-            $inputStartTime = $data['start_time'];
-            if ($itemStartTime > 0 && $inputStartTime < $itemStartTime) {
-                return [false, '任务开始时间不能早于项目节点开始时间:' . date('Y-m-d', $itemStartTime)];
-            }
-        }
-        if (!empty($data['end_time'])) {
-            $inputEndTime = $data['end_time'];
-            if ($itemEndTime > 0 && $inputEndTime > $itemEndTime) {
-                return [false, '任务结束时间不能晚于项目节点结束时间:' . date('Y-m-d', $itemEndTime)];
-            }
-        }
-        if(! empty($data['man_list'])) {
-            foreach ($data['man_list'] as $key => $value){
-                if(empty($value['type'])) return [false, '类型不能为空'];
-                if(empty($value['data_id'])) return [false, '人员不能为空'];
-                $data['man_list'][$key]['top_depart_id'] = $data['top_depart_id'];
-            }
-            list($status, $msg) = $this->checkArrayRepeat($data['man_list'],'data_id','人员');
-            if(! $status) return [false, $msg];
-        }
-        if(! empty($data['device_list'])) {
-            foreach ($data['device_list'] as $key => $value){
-                if(empty($value['type'])) return [false, '类型不能为空'];
-                if(empty($value['data_id'])) return [false, '设备ID不能为空'];
-                $data['device_list'][$key]['top_depart_id'] = $data['top_depart_id'];
-            }
-            list($status, $msg) = $this->checkArrayRepeat($data['device_list'],'data_id','设备');
-            if(! $status) return [false, $msg];
-        }
-
-        if($is_add){
-            $bool = ItemNodeMission::where('item_node_id',$data['item_node_id'])
-                ->where('top_depart_id', $data['top_depart_id'])
-                ->where('title', $data['title'])
-                ->where('del_time',0)
-                ->exists();
-        }else{
-            if(empty($data['id'])) return [false,'ID不能为空'];
-            $item = ItemNodeMission::where('id', $data['id'])
-                ->where('del_time',0)
-                ->first();
-            if(empty($item)) return [false, '项目节点下任务不存在或已被删除'];
-            if($item->approval_state == ItemNodeMission::TYPE_MINUS_ONE) return [false, '任务审核中,操作失败'];
-            if($data['state'] == ItemNodeMission::TYPE_THREE) {
-                $bool = ItemNodeMission::where('del_time',0)
-                    ->where('parent_item_node_mission_id', $data['id'])
-                    ->where('state', '<>',ItemNodeMission::TYPE_THREE)
-                    ->exists();
-                if($bool) return [false, '任务下存在子任务未完结'];
-            }
-
-            $bool = ItemNodeMission::where('title', $data['title'])
-                ->where('item_node_id',$data['item_node_id'])
-                ->where('top_depart_id', $data['top_depart_id'])
-                ->where('id','<>',$data['id'])
-                ->where('del_time',0)
-                ->exists();
-        }
-        if($bool) return [false, '该项目节点下任务名称已存在'];
-
-        //判断是否编辑 然后需要审核的 需要后 就更新状态 记录草稿
-        if(! $is_add){
-            $return = $this->checkItemNodeMissionIsChanged($data, $user);
-            if(is_array($return)) {
-                list($status, $msg) = $return;
-                return [$status, $msg];
-            }else{
-                if($return) $data['draft'] = true;
-            }
-        }
-
-        return [true, ''];
-    }
-
     public function itemNodeMissionRule(&$data, $user, $is_add = true)
     {
         $data['top_depart_id'] = $user['top_depart_id'];
@@ -3295,6 +3107,17 @@ class ItemService extends Service
             ->toArray();
     }
 
+    public function getItemNodeMissionMap($ids){
+        $ids = array_filter((array)$ids);
+        if (empty($ids)) return [];
+
+        return ItemNodeMission::whereIn('id', $ids)
+            ->select('id', 'title', 'code')
+            ->get()
+            ->keyBy('id')
+            ->toArray();
+    }
+
     public function checkIsDelivery($data, $tableName){
         //是否需要交付物完结
         if($data['is_review_required']){
@@ -3363,4 +3186,400 @@ class ItemService extends Service
         list($status, $msg) = (new WorkFlowService())->triggerWorkflow($oldData['review_id'],$oldData['id'],$tableName,$user);
         if(! $status) throw new \Exception($msg);
     }
+
+    // 创建链接分享
+    public function shareAdd($data, $user)
+    {
+        // 执行校验
+        list($status, $msg) = $this->shareRule($data, $user);
+        if (!$status) return [$status, $msg];
+
+        try {
+            DB::beginTransaction();
+
+            $mission = $data['mission_data'];
+
+            // 生成唯一的随机 Token (32位字符串)
+            $shareToken = bin2hex(random_bytes(16));
+
+            $model = new ItemNodeMissionShare();
+            $model->item_id = $mission->item_id;
+            $model->item_node_id = $mission->item_node_id;
+            $model->item_node_mission_id = $mission->id;
+            $model->share_token = $shareToken;
+            $model->expire_time = $data['expire_time'];
+            $model->remark = $data['remark'] ?? '';
+            $model->crt_id = $user['id'];
+            $model->top_depart_id = $data['top_depart_id'];
+            $model->save();
+
+            DB::commit();
+
+            return [true, ''];
+
+        } catch (\Exception $exception) {
+            DB::rollBack();
+            return [false, $exception->getMessage()];
+        }
+    }
+
+    public function shareEdit($data, $user)
+    {
+        // 执行校验
+        list($status, $msg) = $this->shareRule($data, $user, false);
+        if (!$status) return [$status, $msg];
+
+        try {
+            DB::beginTransaction();
+
+            $model = $data['share_model'];
+            $model->expire_time = $data['expire_time'];
+            $model->remark = $data['remark'] ?? '';
+            $model->type = $data['type'] ?? 0;
+            $model->save();
+
+            DB::commit();
+
+            return [true, ''];
+
+        } catch (\Exception $exception) {
+            DB::rollBack();
+            return [false, $exception->getMessage()];
+        }
+    }
+
+    public function shareRule(&$data, $user, $is_add = true)
+    {
+        $data['top_depart_id'] = $user['top_depart_id'];
+        $currentTime = time();
+
+        // 1. 基础参数校验
+        if (empty($data['item_node_mission_id'])) return [false, '请选择任务'];
+        if (empty($data['expire_time'])) return [false, '请选择链接过期时间'];
+        if (! isset($data['type']) || ! isset(ItemNodeMissionShare::QX_Type[$data['type']])) return [false, '链接权限不能为空或错误'];
+
+        $mission = DB::table('item_node_mission')
+            ->where('id', $data['item_node_mission_id'])
+            ->where('del_time', 0)
+            ->first();
+        if (empty($mission)) return [false, '任务不存在或已被删除'];
+        if ($mission->approval_state == -1) return [false, '任务正在审核中,无法生成/编辑分享链接'];
+        $data['item_node_mission_title'] = $mission->title;
+        $data['item_node_title'] = ItemNode::where('id', $mission->item_node_id)->value('title') ?? '';
+        $data['item_title'] = Item::where('id', $mission->item_id)->value('title') ?? '';
+
+        // 时间校验与转换
+        $data['expire_time'] = $this->changeDateToDate($data['expire_time'], true);
+        if ($data['expire_time'] < $currentTime) {
+            return [false, '过期时间不能小于当前时间'];
+        }
+        if ($mission->end_time > 0 && $data['expire_time'] > $mission->end_time) {
+            return [false, '链接过期时间不能超过任务的结束时间'];
+        }
+
+        // 4. 新增 编辑 的差异化校验
+        if ($is_add) {
+            // 【新增场景】同一个任务下的同一个人,只允许存在一个有效的分享链接
+            // 健壮性设计:状态是0 并且 事实上还没过期的链接,都算有效链接
+            $existsValidShare = ItemNodeMissionShare::where('item_node_mission_id', $data['item_node_mission_id'])
+                ->where('status', ItemNodeMissionShare::TYPE_ZERO) // 0-已生效
+                ->where('type', $data['type'])
+                ->where('expire_time', '>', $currentTime) // 必须也没过期
+                ->where('del_time', 0)
+                ->exists();
+
+            $qx = ItemNodeMissionShare::QX_Type[$data['type']] ?? '';
+            if ($existsValidShare) return [false, '该任务下已存在一个生效中的分享链接(权限:' . $qx . ')'];
+        } else {
+            if (empty($data['id'])) return [false, 'ID不能为空'];
+
+            $share = ItemNodeMissionShare::where('id', $data['id'])
+                ->where('top_depart_id', $data['top_depart_id'])
+                ->where('del_time', 0)
+                ->first();
+            if (empty($share)) return [false, '链接不存在或已被删除'];
+
+            // 核心拦截:已过期(1) 或 已作废(2) 或者是时间戳层面的实际过期,均不允许编辑
+            if ($share->status == ItemNodeMissionShare::TYPE_ONE || $share->expire_time <= $currentTime) {
+                return [false, '该分享链接已过期,不允许操作'];
+            }
+            if ($share->status == ItemNodeMissionShare::TYPE_TWO) {
+                return [false, '该分享链接已作废,不允许操作'];
+            }
+
+            // 编辑时查重:排除自身 ID
+            $existsOtherValidShare = ItemNodeMissionShare::where('item_node_mission_id', $data['item_node_mission_id'])
+                ->where('id', '<>', $data['id']) // 排除自己
+                ->where('status', ItemNodeMissionShare::TYPE_ZERO)
+                ->where('type', $share->type)
+                ->where('expire_time', '>', $currentTime)
+                ->where('del_time', 0)
+                ->exists();
+
+            $qx = ItemNodeMissionShare::QX_Type[$share->type] ?? '';
+            if ($existsOtherValidShare) {
+                return [false, '该任务下已存在一个生效中的分享链接(权限:' . $qx . ')'];
+            }
+
+            $data['share_model'] = $share;
+        }
+
+        $data['mission_data'] = $mission;
+
+        return [true, ''];
+    }
+
+    public function shareCommon($data,$user, $field = []){
+        if(empty($field)) $field = ItemNodeMissionShare::$field;
+
+        $model = ItemNodeMissionShare::Clear($user,$data);
+        $model = $model->where('del_time',0)
+            ->select($field)
+            ->orderby('id', 'desc');
+
+        if(! empty($data['item_title'])) $model->where('item_title', 'LIKE', '%'.$data['item_title'].'%');
+        if(! empty($data['item_node_title'])) $model->where('item_node_title', 'LIKE', '%'.$data['item_node_title'].'%');
+        if(! empty($data['item_node_mission_title'])) $model->where('item_node_mission_title', 'LIKE', '%'.$data['item_node_mission_title'].'%');
+        if(! empty($data['id'])) $model->whereIn('id', $data['id']);
+        if(isset($data['status'])) $model->where('status', $data['status']);
+        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 shareList($data,$user){
+        $model = $this->shareCommon($data, $user);
+        $list = $this->limit($model,'',$data);
+        $list = $this->fillShareData($list);
+
+        return [true, $list];
+    }
+
+    public function fillShareData($data){
+        if(empty($data['data'])) return $data;
+
+        $emp = (new EmployeeService())->getEmployeeMap(array_unique(array_column($data['data'],'crt_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]['expire_time'] = $value['expire_time'] ? date('Y-m-d',$value['expire_time']) : '';
+            $data['data'][$key]['crt_name'] = $emp[$value['crt_id']] ?? '';
+            $data['data'][$key]['status_title'] = ItemNodeMissionShare::State_Type[$value['status']] ?? '';
+            $data['data'][$key]['type_title'] = ItemNodeMissionShare::QX_Type[$value['type']] ?? '';
+        }
+
+        return $data;
+    }
+
+    public function shareDetail($data, $user){
+        if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
+        $customer = ItemNodeMissionShare::where('del_time',0)
+            ->where('id',$data['id'])
+            ->first();
+        if(empty($customer)) return [false,'分享链接不存在或已被删除'];
+        $customer = $customer->toArray();
+
+        $customer['expire_time']  = ! empty($customer['expire_time']) ? date("Y-m-d", $customer['expire_time']) : "";
+        $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']): '';
+        $customer['status_title'] = ItemNodeMissionShare::State_Type[$customer['status']] ?? '';
+        $customer['type_title'] = ItemNodeMissionShare::QX_Type[$customer['type']] ?? '';
+
+        return [true, $customer];
+    }
+
+    public function shareCancel($data, $user){
+        if($this->isEmpty($data,'id')) return [false,'请选择数据!'];
+
+        $share = ItemNodeMissionShare::where('id', $data['id'])
+            ->where('del_time', 0)
+            ->first();
+        if (empty($share)) return [false, '分享链接不存在或已被删除'];
+
+        $time = time();
+
+        // 核心拦截:已过期(1) 或 已作废(2) 或者是时间戳层面的实际过期,均不允许编辑
+        if ($share->status == ItemNodeMissionShare::TYPE_ONE || $share->expire_time <= $time) {
+            return [false, '该分享链接已过期,不允许操作'];
+        }
+        if ($share->status == ItemNodeMissionShare::TYPE_TWO) {
+            return [false, '该分享链接已作废,不允许操作'];
+        }
+        try {
+            DB::beginTransaction();
+
+            $share->status = ItemNodeMissionShare::TYPE_TWO;
+            $share->save();
+
+            DB::commit();
+        }catch (\Exception $exception){
+            DB::rollBack();
+            return [false,$exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    public function shareOutSideHTML($data){
+        if(empty($data['share_token'])) return [false, '分享链接不能为空'];
+
+        $share = ItemNodeMissionShare::where('share_token', $data['share_token'])
+            ->where('del_time', 0)
+            ->first();
+        if (empty($share)) return [false, '分享链接不存在或已被删除'];
+        if ($share->status == ItemNodeMissionShare::TYPE_ONE || $share->expire_time <= time()) {
+            return [false, '该分享链接已过期'];
+        }
+        if ($share->status == ItemNodeMissionShare::TYPE_TWO) {
+            return [false, '该分享链接已作废'];
+        }
+
+        $share_array = $share->toArray();
+        $emp = (new EmployeeService())->getEmployeeMap([$share_array['user_id']]);
+        $share_array['user_title'] = $emp[$share_array['user_id']] ?? "";
+        list($status, $msg) = $this->itemNodeMissionDetail(['id' => $share_array['item_node_mission_id']], ['top_depart_id' => $share_array['top_depart_id']]);
+        if(! $status) return [false, $msg];
+        $share_array['item_node_mission_data'] = $msg;
+
+        return [true, $share_array];
+    }
+
+    /**
+     * 外部人员更新任务进展(单条模式)
+     */
+    public function itemNodeMissionUpdateProgressContentOutSide($data, $user)
+    {
+        list($status, $msg) = $this->progressOutsideRule($data, $user);
+        if (!$status) return [$status, $msg];
+
+        try {
+            DB::beginTransaction();
+
+            $insertData = $data['processed_data'];
+            ItemNodeMissionContent::insert($insertData);
+
+            DB::commit();
+        } catch (\Exception $exception) {
+            DB::rollBack();
+            return [false, $exception->getMessage()];
+        }
+
+        return [true, ''];
+    }
+
+    /**
+     * 进展提交校验逻辑
+     */
+    public function progressOutsideRule(&$data, $user)
+    {
+        // 1. 基础校验
+        if (empty($data['id'])) return [false, '任务ID不能为空'];
+
+        $customer = ItemNodeMission::where('del_time', 0)
+            ->where('id', $data['id'])
+            ->first();
+        if (empty($customer)) return [false, '项目节点任务不存在或已被删除'];
+        $customer = $customer->toArray();
+
+        if (empty($data['data']) || !is_array($data['data'])) return [false, '任务进展明细不能为空'];
+
+        // 强制获取第一条数据(确保单条限制)
+        $value = reset($data['data']);
+        $empId = $user['id'] ?? 0;
+
+        // 2. 日期校验
+        if (empty($value['order_time'])) return [false, '提交日期不能为空'];
+        $order_time = $this->changeDateToDate($value['order_time']);
+        if ($order_time > $customer['end_time'] || $order_time < $customer['start_time']) {
+            return [false, "提交日期必须在任务周期内"];
+        }
+
+        // 3. 数字与合法性校验
+        $res = $this->checkNumber($value['start_time_hour'], 0, 'non-negative');
+        if (!$res['valid']) return [false, "开始点:" . $res['error']];
+        if ($value['start_time_hour'] > 23) return [false, "开始点不合法"];
+
+        $res = $this->checkNumber($value['start_time_min'], 0, 'non-negative');
+        if (!$res['valid']) return [false, "开始分:" . $res['error']];
+        if ($value['start_time_min'] > 59) return [false, "开始分不合法"];
+
+        $res = $this->checkNumber($value['end_time_hour'], 0, 'non-negative');
+        if (!$res['valid']) return [false, "结束点:" . $res['error']];
+        if ($value['end_time_hour'] > 24) return [false, "结束点不合法"];
+
+        $res = $this->checkNumber($value['end_time_min'], 0, 'non-negative');
+        if (!$res['valid']) return [false, "结束分:" . $res['error']];
+        if ($value['end_time_min'] > 59) return [false, "结束分不合法"];
+
+        // 4. 计算当前提交的时间段(分钟数)
+        $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, "开始时间必须早于结束时间"];
+        }
+
+        $calculatedTotal = $currentEnd - $currentStart;
+        if (!isset($value['total_work_min']) || intval($value['total_work_min']) !== $calculatedTotal) {
+            return [false, "工时计算有误,应为 {$calculatedTotal} 分钟"];
+        }
+
+        $hasDbOverlap = ItemNodeMissionContent::where('del_time', 0)
+            ->where('crt_id', $empId)
+            ->where('order_time', $order_time)
+            ->where('item_node_mission_id', '<>', $customer['id'])
+            ->where(function ($query) use ($value) {
+
+                // 【条件一】:数据库开始时间 < 前端结束时间
+                $query->where(function ($q) use ($value) {
+                    $q->where('start_time_hour', '<', $value['end_time_hour'])
+                        ->orWhere(function ($sub) use ($value) {
+                            $sub->where('start_time_hour', '=', $value['end_time_hour'])
+                                ->where('start_time_min', '<', $value['end_time_min']);
+                        });
+                })
+
+                    // 【条件二】:前端开始时间 < 数据库结束时间
+                    ->where(function ($q) use ($value) {
+                        $q->where('end_time_hour', '>', $value['start_time_hour'])
+                            ->orWhere(function ($sub) use ($value) {
+                                $sub->where('end_time_hour', '=', $value['start_time_hour'])
+                                    ->where('end_time_min', '>', $value['start_time_min']);
+                            });
+                    });
+
+            })
+            ->exists();
+
+        if ($hasDbOverlap) {
+            return [false, "您在该日期(" . date('Y-m-d', $order_time) . ")下已提交过冲突的工时阶段"];
+        }
+
+        // 6. 拼装标准的单条入库数据结构(包在数组里以适配单次批量 insert)
+        $processedData = [
+            [
+                'item_id'               => $customer['item_id'],
+                'item_node_id'          => $customer['item_node_id'],
+                'item_node_mission_id'  => $customer['id'],
+                'top_depart_id'         => $user['top_depart_id'],
+                'crt_id'                => $empId,
+                'order_time'            => $order_time,
+                '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'        => $calculatedTotal,
+                'mark'                  => $value['mark'] ?? '',
+                'crt_time'              => time(),
+                'upd_time'              => time(),
+            ]
+        ];
+
+        $data['processed_data'] = $processedData;
+
+        return [true, ''];
+    }
 }

+ 18 - 1
routes/api.php

@@ -20,6 +20,9 @@ Route::any('aa', 'Api\TestController@aa');
 Route::any('uploadFiles/{file_name}', 'Api\FileUploadController@getFile');
 Route::any('getExport/{file_name}','Api\ExportFileController@getExport');
 
+//分享外部链接
+Route::any('shareOutSideHTML', 'Api\ItemController@shareOutSideHTML');
+
 Route::group(['middleware'=> ['checkLogin']],function ($route){
     //文件上传统一方法
     $route->any('uploadFile', 'Api\FileUploadController@uploadFile');
@@ -130,6 +133,18 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     $route->any('itemGannetList', 'Api\ItemController@itemGannetList');
     $route->any('itemGannetGetSonData', 'Api\ItemController@itemGannetGetSonData');
 
+    //外部获取进展
+    $route->any('itemMissionProgressBoard', 'Api\ItemController@itemMissionProgressBoard');
+    //外部链接登录 更新自己的进展
+    $route->any('itemNodeMissionUpdateProgressContentOutSide', 'Api\ItemController@itemNodeMissionUpdateProgressContentOutSide');
+
+    //分享任务链接
+    $route->any('shareAdd', 'Api\ItemController@shareAdd');
+    $route->any('shareEdit', 'Api\ItemController@shareEdit');
+    $route->any('shareList', 'Api\ItemController@shareList');
+    $route->any('shareDetail', 'Api\ItemController@shareDetail');
+    $route->any('shareCancel', 'Api\ItemController@shareCancel');
+
     //标签管理
     $route->any('tagList', 'Api\TagController@tagList');
     $route->any('tagEdit', 'Api\TagController@tagEdit');
@@ -289,9 +304,11 @@ Route::group(['middleware'=> ['checkLogin']],function ($route){
     //人员月度工资
     $route->any('employeeMonthSalaryStatisticCount', 'Api\StatisticController@employeeMonthSalaryStatisticCount');
 
-    //首页BI数据
+    //工时 首页BI数据
     $route->any('homePageData', 'Api\BIController@homePageData');
     //驾驶舱
     $route->any('cockpit', 'Api\BIController@cockpit');
+    //项目管理 首页BI数据
+    $route->any('homePageItemData', 'Api\BIController@homePageItemData');
 });