|
|
@@ -2664,6 +2664,8 @@ class ItemService extends Service
|
|
|
->first();
|
|
|
if(empty($customer)) return [false,'项目节点任务不存在或已被删除'];
|
|
|
$customer = $customer->toArray();
|
|
|
+ if($customer['state'] == ItemNodeMission::TYPE_THREE) return [false, '任务已完成'];
|
|
|
+
|
|
|
if(empty($data['data'])) return [false, '任务进展明细不能为空'];
|
|
|
|
|
|
$empDisplayMap = Employee::whereIn('id', array_unique(array_column($data['data'],'data_id')))
|
|
|
@@ -2731,7 +2733,9 @@ class ItemService extends Service
|
|
|
DB::beginTransaction();
|
|
|
$time = time();
|
|
|
|
|
|
- ItemNodeMissionContent::where('item_node_mission_id', $data['id'])->where('del_time',0)->update(['del_time' => $time]);
|
|
|
+ ItemNodeMissionContent::where('item_node_mission_id', $data['id'])
|
|
|
+ ->where('del_time',0)
|
|
|
+ ->update(['del_time' => $time]);
|
|
|
|
|
|
ItemNodeMissionContent::insert($data['data']);
|
|
|
|
|
|
@@ -2746,12 +2750,236 @@ class ItemService extends Service
|
|
|
|
|
|
public function itemNodeMissionUpdateProgress($data, $user){
|
|
|
if(empty($data['id'])) return [false, 'ID不能为空'];
|
|
|
+ $customer = ItemNodeMission::where('del_time',0)
|
|
|
+ ->where('id',$data['id'])
|
|
|
+ ->first();
|
|
|
+ if(empty($customer)) return [false,'项目节点任务不存在或已被删除'];
|
|
|
+ $order = $customer->toArray();
|
|
|
+ if($order['state'] == ItemNodeMission::TYPE_THREE) return [false, '任务已完成'];
|
|
|
if(empty($data['progress'])) return [false, '任务进展不能为空'];
|
|
|
$res = $this->checkNumber($data['progress'],0,'positive');
|
|
|
if (! $res['valid']) return [false, "任务进展:" . $res['error']];
|
|
|
if($data['progress'] > 100) return [false, '任务进展不能超过100'];
|
|
|
|
|
|
- ItemNodeMission::where('id', $data['id'])->update(['progress' => $data['progress']]);
|
|
|
+ $customer->progress = $data['progress'];
|
|
|
+ $customer->save();
|
|
|
+
|
|
|
+ return [true, ''];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function itemNodeMissionBatchUpdateProgressContent($data, $user)
|
|
|
+ {
|
|
|
+ if (empty($data['data']) || !is_array($data['data'])) {
|
|
|
+ return [false, '任务进展明细不能为空'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 批量收集涉及的所有任务 ID 和人员 ID,用于前置内存缓存
|
|
|
+ $missionIds = array_filter(array_unique(array_column($data['data'], 'item_node_mission_id')));
|
|
|
+ $empIds = array_filter(array_unique(array_column($data['data'], 'data_id')));
|
|
|
+ $detailIds = array_filter(array_unique(array_column($data['data'], 'id'))); // 收集所有传了的明细ID
|
|
|
+
|
|
|
+ if (empty($missionIds)) return [false, '明细中缺少有效的任务ID'];
|
|
|
+
|
|
|
+ // 2. 批量查出相关任务和人员花名册,杜绝循环查 SQL
|
|
|
+ $missionsMap = ItemNodeMission::where('del_time', 0)->whereIn('id', $missionIds)->get()->keyBy('id')->toArray();
|
|
|
+ $empDisplayMap = Employee::whereIn('id', $empIds)->get(['id', 'number', 'title'])->mapWithKeys(function($item){
|
|
|
+ return [$item->id => "[{$item->number}]{$item->title}"];
|
|
|
+ })->toArray();
|
|
|
+
|
|
|
+ // 3. 一次性把这些任务/人在这些日期的已有数据库报告捞出来(用于做高效率的重叠比对)
|
|
|
+ // 这样就不用在 for 循环里去一条条 count 查重了
|
|
|
+ $dbPeriods = ItemNodeMissionContent::where('del_time', 0)
|
|
|
+ ->whereIn('item_node_mission_id', $missionIds)
|
|
|
+ ->whereIn('data_id', $empIds)
|
|
|
+ ->get(['id', 'item_node_mission_id', 'data_id', 'order_time', 'start_time_hour', 'start_time_min', 'end_time_hour', 'end_time_min'])
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ // 用于校验“本次提交内部”时间段是否重叠
|
|
|
+ $internalOverlap = [];
|
|
|
+ // 最终准备执行的数据集
|
|
|
+ $finalRows = [];
|
|
|
+
|
|
|
+ // 4. 循环校验
|
|
|
+ foreach ($data['data'] as $key => $value) {
|
|
|
+ $line = $key + 1;
|
|
|
+ $t = "第 " . $line . " 行:";
|
|
|
+
|
|
|
+ $detailId = $value['id'] ?? 0; // 明细自增ID(有代表修改,无代表新增)
|
|
|
+ $missionId = $value['item_node_mission_id'] ?? 0;
|
|
|
+ $empId = $value['data_id'] ?? 0;
|
|
|
+
|
|
|
+ if (empty($missionId) || empty($empId)) return [false, $t . '任务ID和人员ID不能为空'];
|
|
|
+ if (!isset($missionsMap[$missionId])) return [false, $t . "任务[ID:{$missionId}]不存在"];
|
|
|
+
|
|
|
+ $customer = $missionsMap[$missionId];
|
|
|
+ $empName = $empDisplayMap[$empId] ?? "ID:{$empId}";
|
|
|
+
|
|
|
+ if ($customer['state'] == ItemNodeMission::TYPE_THREE) {
|
|
|
+ return [false, $t . "任务【{$customer['title']}】已完成,不可操作"];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 日期校验
|
|
|
+ if (empty($value['order_time'])) return [false, $t . '提交日期不能为空'];
|
|
|
+ $order_time = $this->changeDateToDate($value['order_time']);
|
|
|
+ if ($order_time > $customer['end_time'] || $order_time < $customer['start_time']) {
|
|
|
+ return [false, $t . "人员{$empName}的提交日期必须在任务周期内"];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 基础数字校验 (调用你的校验函数)
|
|
|
+ if ($value['start_time_hour'] > 23 || $value['start_time_min'] > 59 || $value['end_time_hour'] > 24 || $value['end_time_min'] > 59) {
|
|
|
+ return [false, $t . "人员{$empName}提交的时间点不合法"];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转化为分钟数
|
|
|
+ $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, $t . "人员{$empName}:开始时间必须早于结束时间"];
|
|
|
+
|
|
|
+ // 总分钟数校验
|
|
|
+ $calculatedTotal = $currentEnd - $currentStart;
|
|
|
+ if (!isset($value['total_work_min']) || intval($value['total_work_min']) !== $calculatedTotal) {
|
|
|
+ return [false, $t . "人员{$empName}:工时计算有误,应为 {$calculatedTotal} 分钟"];
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- 5. 校验【本次提交内部】的重叠 ---
|
|
|
+ if (isset($internalOverlap[$missionId][$empId][$order_time])) {
|
|
|
+ foreach ($internalOverlap[$missionId][$empId][$order_time] as $period) {
|
|
|
+ if ($currentStart < $period['e'] && $period['s'] < $currentEnd) {
|
|
|
+ return [false, $t . "人员{$empName}在本次提交的多行明细中时间段重叠"];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $internalOverlap[$missionId][$empId][$order_time][] = ['s' => $currentStart, 'e' => $currentEnd];
|
|
|
+
|
|
|
+ // --- 6. 核心:校验【与数据库已有数据】的重叠 ---
|
|
|
+ foreach ($dbPeriods as $dbRow) {
|
|
|
+ // 只有当 任务、人、日期 完全一致时,才需要比对时间段
|
|
|
+ if ($dbRow['item_node_mission_id'] == $missionId && $dbRow['data_id'] == $empId && $dbRow['order_time'] == $order_time) {
|
|
|
+
|
|
|
+ // 【核心优化点】:如果是编辑状态,排查重叠时必须“排除掉自己这条记录”
|
|
|
+ if ($detailId > 0 && $dbRow['id'] == $detailId) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $dbStart = $dbRow['start_time_hour'] * 60 + $dbRow['start_time_min'];
|
|
|
+ $dbEnd = $dbRow['end_time_hour'] * 60 + $dbRow['end_time_min'];
|
|
|
+
|
|
|
+ // 判断时间段是否有交集:开始A < 结束B 并且 开始B < 结束A
|
|
|
+ if ($currentStart < $dbEnd && $dbStart < $currentEnd) {
|
|
|
+ return [false, $t . "人员{$empName}在系统已有记录中存在重叠的时间段({$dbRow['start_time_hour']}:{$dbRow['start_time_min']} ~ {$dbRow['end_time_hour']}:{$dbRow['end_time_min']})"];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 收集待处理数据
|
|
|
+ $finalRows[] = [
|
|
|
+ 'id' => $detailId, // 捎带上ID,后面分流用
|
|
|
+ 'item_id' => $customer['item_id'],
|
|
|
+ 'item_node_id' => $customer['item_node_id'],
|
|
|
+ 'item_node_mission_id' => $missionId,
|
|
|
+ 'data_id' => $empId,
|
|
|
+ 'content' => $value['content'] ?? '',
|
|
|
+ 'order_time' => $order_time,
|
|
|
+ 'total_work_min' => $calculatedTotal,
|
|
|
+ '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'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 事务处理:分流执行 Update 和 Insert
|
|
|
+ try {
|
|
|
+ DB::beginTransaction();
|
|
|
+ $time = time();
|
|
|
+
|
|
|
+ $inserts = [];
|
|
|
+ foreach ($finalRows as $row) {
|
|
|
+ $id = $row['id'];
|
|
|
+ unset($row['id']); // 从数组中剥离自增 ID
|
|
|
+
|
|
|
+ if ($id > 0) {
|
|
|
+ // 情况 A:有 ID,执行精准修改
|
|
|
+ ItemNodeMissionContent::where('id', $id)->update($row);
|
|
|
+ } else {
|
|
|
+ // 情况 B:没有 ID,放入待插入队列
|
|
|
+ $row['top_depart_id'] = $user['top_depart_id'];
|
|
|
+ $row['crt_id'] = $user['id'];
|
|
|
+ $row['crt_time'] = $time;
|
|
|
+ $inserts[] = $row;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量插入新数据
|
|
|
+ if (!empty($inserts)) {
|
|
|
+ ItemNodeMissionContent::insert($inserts);
|
|
|
+ }
|
|
|
+
|
|
|
+ DB::commit();
|
|
|
+ } catch (\Exception $exception) {
|
|
|
+ DB::rollBack();
|
|
|
+ return [false, '保存失败:' . $exception->getMessage()];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [true, ''];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量删除任务报告
|
|
|
+ * @param array $data ['id' => [1, 2, 3...]] 报告ID数组
|
|
|
+ * @param array $user 当前登录人信息
|
|
|
+ * @return array [bool, string]
|
|
|
+ */
|
|
|
+ public function itemNodeMissionContentDelete($data, $user)
|
|
|
+ {
|
|
|
+ // 兼容前端可能传单个 ID 或 ID 数组的情况
|
|
|
+ $ids = isset($data['id']) ? (array)$data['id'] : [];
|
|
|
+ $ids = array_filter(array_unique($ids));
|
|
|
+
|
|
|
+ if (empty($ids)) {
|
|
|
+ return [false, '请选择要删除的任务报告'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 连表查询这些报告所属任务的状态
|
|
|
+ // 目的:找出有没有属于“已完成”状态任务的报告
|
|
|
+ $completedMissions = DB::table('item_node_mission_content as e')
|
|
|
+ ->join('item_node_mission as i', 'e.item_node_mission_id', '=', 'i.id')
|
|
|
+ ->whereIn('e.id', $ids)
|
|
|
+ ->where('e.del_time', 0)
|
|
|
+ ->where('i.state', ItemNodeMission::TYPE_THREE) // 任务已完成状态
|
|
|
+ ->select('e.id as content_id', 'i.title as mission_title')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ // 2. 核心校验:如果存在已完成任务的报告,直接拦截报错
|
|
|
+ if ($completedMissions->isNotEmpty()) {
|
|
|
+ $firstError = $completedMissions->first();
|
|
|
+ return [false, "任务【{$firstError->mission_title}】已完成,不允许删除该任务下的任何进展报告"];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 事务处理:批量执行软删除
|
|
|
+ try {
|
|
|
+ DB::beginTransaction();
|
|
|
+ $time = time();
|
|
|
+
|
|
|
+ // 批量将这些报告的 del_time 更新为当前时间戳(软删除)
|
|
|
+ $effectRows = ItemNodeMissionContent::whereIn('id', $ids)
|
|
|
+ ->where('del_time', 0)
|
|
|
+ ->update([
|
|
|
+ 'del_time' => $time,
|
|
|
+ 'upd_time' => $time // 顺便把更新时间也同步了
|
|
|
+ ]);
|
|
|
+
|
|
|
+ if ($effectRows === 0) {
|
|
|
+ DB::rollBack();
|
|
|
+ return [false, '未找到有效的任务报告或报告已被删除'];
|
|
|
+ }
|
|
|
+
|
|
|
+ DB::commit();
|
|
|
+ } catch (\Exception $exception) {
|
|
|
+ DB::rollBack();
|
|
|
+ return [false, '删除失败:' . $exception->getMessage()];
|
|
|
+ }
|
|
|
|
|
|
return [true, ''];
|
|
|
}
|
|
|
@@ -2948,6 +3176,7 @@ class ItemService extends Service
|
|
|
|
|
|
$item_id = $data['item_id'] ?? 0;
|
|
|
$item_node_id = $data['item_node_id'] ?? 0;
|
|
|
+ $except_state = $data['except_state'] ?? 0;
|
|
|
$model = ItemNodeMission::TopAndEmployeeClear($user,$data);
|
|
|
$model = $model->where('del_time',0)
|
|
|
->when(! empty($item_id),function ($query) use($item_id){
|
|
|
@@ -2956,6 +3185,9 @@ class ItemService extends Service
|
|
|
->when(! empty($item_node_id),function ($query) use($item_node_id){
|
|
|
return $query->where('item_node_id', $item_node_id);
|
|
|
})
|
|
|
+ ->when(! empty($except_state),function ($query) use($except_state){
|
|
|
+ return $query->whereNotIn('state', (array)$except_state);
|
|
|
+ })
|
|
|
->select($field)
|
|
|
->orderby('id', 'desc');
|
|
|
|
|
|
@@ -2977,7 +3209,7 @@ class ItemService extends Service
|
|
|
public function itemNodeMissionList($data,$user){
|
|
|
$model = $this->itemNodeMissionCommon($data, $user);
|
|
|
$list = $this->limit($model,'',$data);
|
|
|
- $list = $this->fillNodeMissionData($list);
|
|
|
+ $list = $this->fillNodeMissionData($list, $data, $user);
|
|
|
|
|
|
return [true, $list];
|
|
|
}
|
|
|
@@ -2987,7 +3219,7 @@ class ItemService extends Service
|
|
|
if(! $status) return [false, $model];
|
|
|
|
|
|
$list['data'] = $model->get()->toArray();
|
|
|
- $list = $this->fillNodeMissionData($list);
|
|
|
+ $list = $this->fillNodeMissionData($list, $data, $user);
|
|
|
|
|
|
return [true, $list['data']];
|
|
|
}
|
|
|
@@ -3196,12 +3428,14 @@ class ItemService extends Service
|
|
|
return false; // 没有任何变动
|
|
|
}
|
|
|
|
|
|
- public function fillNodeMissionData($data){
|
|
|
+ public function fillNodeMissionData($data, $ergs, $user){
|
|
|
if(empty($data['data'])) return $data;
|
|
|
|
|
|
$emp = (new EmployeeService())->getEmployeeMap(array_unique(array_merge_recursive(array_column($data['data'],'charge_id'), array_column($data['data'],'crt_id'))));
|
|
|
$tag = (new TagService())->getTagMap(array_unique(array_merge_recursive(array_column($data['data'],'priority_id'),array_column($data['data'],'mission_id'))));
|
|
|
- $item_node_map = $this->getItemNodeMap(array_column($data['data'],'id'));
|
|
|
+ $mission_id = array_column($data['data'],'id');
|
|
|
+ $item_node_map = $this->getItemNodeMap($mission_id);
|
|
|
+ if(isset($ergs['get_item_node_mission_man'])) $mission_man = $this->getMissionMembersMap($mission_id, $user['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]['start_time'] = $value['start_time'] ? date('Y-m-d',$value['start_time']) : '';
|
|
|
@@ -3219,11 +3453,77 @@ class ItemService extends Service
|
|
|
$data['data'][$key]['mission_code'] = $node_tmp['code'] ?? '';
|
|
|
$data['data'][$key]['mission_color'] = $node_tmp['color'] ?? '';
|
|
|
$data['data'][$key]['item_node_title'] = $item_node_map[$value['id']] ?? '';
|
|
|
+ $data['data'][$key]['mission_man'] = $mission_man[$value['id']] ?? [];
|
|
|
}
|
|
|
|
|
|
return $data;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 根据任务 ID 数组获取当前登录人可见的任务成员 Map 结构(传统数组对象结构)
|
|
|
+ * * @param array $missionIds 任务 ID 数组 [1, 2, 3...]
|
|
|
+ * @param int $userId 当前登录人 ID
|
|
|
+ * @return array [mission_id => [ ["id" => 1, "title" => "张三"], ... ]]
|
|
|
+ */
|
|
|
+ public function getMissionMembersMap(array $missionIds, int $userId)
|
|
|
+ {
|
|
|
+ if (empty($missionIds)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 批量查出当前用户在这些任务里哪些是【负责人】身份
|
|
|
+ $chargeMissionIds = \DB::table('item_node_mission_employee')
|
|
|
+ ->whereIn('item_node_mission_id', $missionIds)
|
|
|
+ ->where('data_id', $userId)
|
|
|
+ ->where('del_time', 0)
|
|
|
+ ->pluck('item_node_mission_id')
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ // 2. 准备查询成员表的 Query,并连表关联 employee
|
|
|
+ $query = \DB::table('item_node_mission_details as d')
|
|
|
+ ->join('employee as e', 'd.data_id', '=', 'e.id')
|
|
|
+ ->whereIn('d.item_node_mission_id', $missionIds)
|
|
|
+ ->where('d.type', 1)
|
|
|
+ ->where('d.del_time', 0)
|
|
|
+ ->where('e.del_time', 0)
|
|
|
+ ->select('d.item_node_mission_id', 'd.data_id as id', 'e.title'); // 别名直接对齐你需要的 id 和 title
|
|
|
+
|
|
|
+ // 3. 核心分流:找出哪些任务用户【不是负责人】
|
|
|
+ $notChargeMissionIds = array_diff($missionIds, $chargeMissionIds);
|
|
|
+
|
|
|
+ if (!empty($notChargeMissionIds)) {
|
|
|
+ $query->where(function ($q) use ($chargeMissionIds, $userId) {
|
|
|
+ if (!empty($chargeMissionIds)) {
|
|
|
+ $q->whereIn('d.item_node_mission_id', $chargeMissionIds)
|
|
|
+ ->orWhere(function ($subQ) use ($userId) {
|
|
|
+ $subQ->where('d.data_id', $userId);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ $q->where('d.data_id', $userId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 查出过滤后的所有成员数据
|
|
|
+ $details = $query->get();
|
|
|
+
|
|
|
+ // 5. 初始化 Map 壳子
|
|
|
+ $resultMap = [];
|
|
|
+ foreach ($missionIds as $id) {
|
|
|
+ $resultMap[$id] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 组装成你需要的规范二维结构
|
|
|
+ foreach ($details as $detail) {
|
|
|
+ $resultMap[$detail->item_node_mission_id][] = [
|
|
|
+ 'id' => $detail->id,
|
|
|
+ 'title' => $detail->title
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
public function getItemNodeMap($item_node_mission_id){
|
|
|
if(! is_array($item_node_mission_id)) $item_node_mission_id = [$item_node_mission_id];
|
|
|
return ItemNodeMission::from('item_node_mission as a')
|
|
|
@@ -3652,6 +3952,7 @@ class ItemService extends Service
|
|
|
->first();
|
|
|
if (empty($customer)) return [false, '项目节点任务不存在或已被删除'];
|
|
|
$customer = $customer->toArray();
|
|
|
+ if($customer['state'] == ItemNodeMission::TYPE_THREE) return [false, '任务已完成'];
|
|
|
|
|
|
if (empty($data['data']) || !is_array($data['data'])) return [false, '任务进展明细不能为空'];
|
|
|
|