validator; } /** * 将二维数组按指定 key 重组为关联数组,该 key 的值作为新数组的键, * 其余元素组成子数组作为值。 * * @param array $array 原始二维数组 * @param string $keyField 用作新键的字段名(如 'id') * @return array 重组后的数组 */ private function groupArrayByKey($array, $keyField = 'id') { $result = []; foreach ($array as $item) { if (!isset($item[$keyField])) { continue; // 跳过没有该 key 的项 } $key = $item[$keyField]; // 移除用作键的字段 unset($item[$keyField]); $result[$key] = $item; } return $result; } /** * 创建物品归还申请前置处理 * * @param array $data 归还申请数据 * @param Request $request 请求对象 * @return array 处理后的数据 */ public function beforeServiceStore($data, Request $request): array { try { // // 模拟数据 // $data = [ // [ // 'requisition_item_id' => 9, // 'return_qty' => 1, // 'apply_remark' => '申请备注', // 'attachment' => [ // [ // 'original_name' => '附件1.jpg', // 'file_path' => '/uploads/2023/07/01/attachment1.jpg' // ], // [ // 'original_name' => '附件2.jpg', // 'file_path' => '/uploads/2023/07/01/attachment2.jpg' // ] // ] // ] // ]; // 获取所有申请项ID $reqItemIds = array_column($data['items'], 'requisition_item_id'); // 参数校验 if (empty($reqItemIds)) { throw new BusinessException('未提供有效的申请项ID'); } // 将申请项按 ID 重组为关联数组 $newReqItems = $this->groupArrayByKey($data['items'], 'requisition_item_id'); $data['return_at'] = is_int($data['return_at']) ? $data['return_at']: strtotime($data['return_at']); // 获取申请项明细 $reqItemsArr = InvRequisitionItem::whereIn('id', $reqItemIds)->get()->toArray(); // 检查是否获取到有效数据 if (empty($reqItemsArr)) { throw new BusinessException('未找到对应的申请项'); } // 获取用户信息 $userService = new UserService(); $user = $userService->getUserInfo($request->user->uid, ''); $data['applicant_id'] = $user['user']['id']; $data['applicant_name'] = $user['user']['name']; //$reqItemId = $data['requisition_id']; $arr = []; foreach ($reqItemsArr as $reqItem) { $reqItemId = $reqItem['id']; // 检查是否有对应的新申请项数据 if (!isset($newReqItems[$reqItemId])) { continue; } $item = InvItem::where('id', $reqItem['item_id'])->first(); $manage = User::where('id',$item['manage_user_id'])->first(); $arr[] = [ 'requisition_id' => $data['requisition_id'], 'requisition_item_id' => $reqItem['id'], 'item_id' => $reqItem['item_id'], 'apply_user_id' => $user['user']['id'], 'apply_user_name' => $user['user']['name'], 'return_qty' => $newReqItems[$reqItemId]['return_qty'] ?: '1', 'apply_remark' => $data['apply_remark'] ?? '', 'handler_id' => $item['manage_user_id'] ?? 0, 'handler_name' => $manage['name'] ?? '', //'attachment' => $newReqItems[$reqItemId]['attachment'], //'return_at' => time(), 'return_at' => $data['return_at'], ]; $data['handler_id'] = $item['manage_user_id'] ?? 0; $data['handler_name'] = $manage['name'] ?? ''; } $data['items'] =$arr; return $data; } catch (BusinessException $e) { throw $e; } catch (\Throwable $th) { throw new BusinessException('创建物品归还申请失败:' . $th->getMessage()); } } /** * 处理物品申请 * * @param Request $request * @param array $data * @return array */ public function serviceStore(Request $request, array $data, string $scene = '') { try { // 关联表增加事务处理 return Db::transaction(function () use ($request, $data) { $data = $this->preProcess('Store', $data, $request); //$dl = $this->getDataLimitFromRequest($request); $items = $data['items']; $attachment = $data['attachment']; unset($data['items']); unset($data['attachment']); $data['order_no'] = $this->generateOrderNo(); $res = InvRequisitionReturn::create($data); $created = []; foreach ($items as $item) { //$created = $this->logicCreate($item, $dl); $item['return_id'] = $res->id; $item['requisition_id'] = $data['requisition_id']; $item['return_at'] = $data['return_at']; $item['apply_remark'] = $data['apply_remark']; $created = $this->logicCreate($item); } $this->processRequisitionItemReturnAttachment($res, $attachment); InvRequisition::where('id',$request->post('requisition_id'))->update( ['status' => 14] //审批状态:10默认、11审批中、12已审批(待归还)、13拒绝、14申请归还、15归还审核中、16部分归还、17已全部归还、18已拒绝、19已审批(无需归还) ); $this->postProcess('Store', $created); return $res; }); } catch (\Throwable $e) { throw new BusinessException("创建物品申请失败:" . $e->getMessage().$e->getTraceAsString()); } } /** * 更新物品归还申请前置处理 * * @param array $data 归还申请数据 * @param Request $request 请求对象 * @return array 处理后的数据 */ public function beforeServiceUpdate($data, Request $request): array { // // 模拟数据 // $data = [ // 'id' => 1, // 'requisition_item_id' => 9, // 'return_qty' => 1, // 'apply_remark' => '申请备注22', // 'attachment' => [ // [ // 'original_name' => '附件1.jpg', // 'file_path' => '/uploads/2023/07/01/attachment1.jpg' // ], // [ // 'original_name' => '附件2.jpg', // 'file_path' => '/uploads/2023/07/01/attachment2.jpg' // ], // [ // 'original_name' => '附件3.jpg', // 'file_path' => '/uploads/2023/07/01/attachment3.jpg' // ] // ] // ]; try { $approved = InvRequisitionItemReturn::where('return_id',$data['id']) ->where('status','>',0) ->count(); if ($approved > 0) { throw new BusinessException('已审核禁止编辑'); } // 获取所有申请项ID $reqItemIds = array_column($data['items'], 'requisition_item_id'); // 参数校验 if (empty($reqItemIds)) { throw new BusinessException('未提供有效的申请项ID'); } // 将申请项按 ID 重组为关联数组 $newReqItems = $this->groupArrayByKey($data['items'], 'requisition_item_id'); $data['return_at'] = is_int($data['return_at']) ? $data['return_at']: strtotime($data['return_at']); // 获取申请项明细 $reqItemsArr = InvRequisitionItem::whereIn('id', $reqItemIds)->get()->toArray(); // 检查是否获取到有效数据 if (empty($reqItemsArr)) { throw new BusinessException('未找到对应的申请项'); } // 获取用户信息 $userService = new UserService(); $user = $userService->getUserInfo($request->user->uid, ''); $data['applicant_id'] = $user['user']['id']; $data['applicant_name'] = $user['user']['name']; //$reqItemId = $data['requisition_id']; $arr = []; foreach ($reqItemsArr as $reqItem) { $reqItemId = $reqItem['id']; // 检查是否有对应的新申请项数据 if (!isset($newReqItems[$reqItemId])) { continue; } $arr[] = [ 'requisition_id' => $data['requisition_id'], 'requisition_item_id' => $reqItem['id'], 'item_id' => $reqItem['item_id'], 'apply_user_id' => $user['user']['id'], 'apply_user_name' => $user['user']['name'], 'return_qty' => $newReqItems[$reqItemId]['return_qty'] ?: '1', 'apply_remark' => $data['apply_remark'] ?? '', //'attachment' => $newReqItems[$reqItemId]['attachment'], //'return_at' => time(), 'return_at' => $data['return_at'], ]; } $data['items'] =$arr; return $data; } catch (BusinessException $e) { throw $e; } catch (\Throwable $th) { throw new BusinessException('创建物品归还申请失败:' . $th->getMessage()); } } /** * 更新物品归还申请 * * @param Request $request * @param array $data * @return array */ public function serviceUpdate(Request $request, array $data, string $scene = '') { try { // 关联表增加事务处理 return Db::transaction(function () use ($request, $data) { $data = $this->preProcess('Update', $data, $request); //$config = $this->buildQueryConfig($request, 'update'); $attachment = $data['attachment']; unset($data['attachment']); $items = $data['items']; unset($data['items']); $config = []; $config['model'] = InvRequisitionReturn::class; $config['where']['id'] = $request->post('id'); $returnService = new InvRequisitionReturnService(); $returnService->logicUpdateBy($config, $data); InvRequisitionItemReturn::where('return_id',$data['id'])->delete(); foreach ($items as $item) { $item['return_id'] = $data['id']; $item['requisition_id'] = $data['requisition_id']; $item['return_at'] = $data['return_at']; $item['apply_remark'] = $data['apply_remark']; $created = $this->logicCreate($item); //$this->logicUpdateBy($config, $data); } // 获取更新后的完整记录 $updated = $returnService->logicShow($config); // 处理附件更新 $this->processRequisitionItemReturnAttachment($updated, $attachment, true); return $this->postProcess('Update', $updated); }); } catch (\Throwable $e) { throw new BusinessException("更新物品归还申请失败:" . $e->getMessage().$e->getLine().$e->getFile().$e->getTraceAsString()); } } /** * 处理物品申请附件 * * @param Model $created * @param array $items * @param bool $isUpdate * @return void */ private function processRequisitionItemReturnAttachment(Model $created, array $attachment, bool $isUpdate = false): void { if ($isUpdate) { $this->clearRequisitionItemReturnAttachment($created->id); } foreach ($attachment as $file) { $attachmentData = [ 'return_id' => $created->id, 'file_name' => $file['original_name'], 'file_path' => $file['file_path'] ]; InvRequisitionItemReturnAttachment::create($attachmentData); } } /** * 清空物品申请附件 * * @param int $returnId * @return void */ private function clearRequisitionItemReturnAttachment(int $returnId): void { InvRequisitionItemReturnAttachment::where('return_id', $returnId)->delete(); } /** * 物品领用归还确认 * * @param Request $request * @param array $data * @param string $scene * @return array|bool * @throws BusinessException */ public function serviceReturnItemConfirm(Request $request, array $data, string $scene = 'returnConfirm') { try { $attachment = $request->post('attachment') ?? []; unset($data['attachment']); // 归还确认是业务顶层方法,这里统一使用事务 return Db::transaction(function () use ($request, $data, $scene, $attachment) { // 1. 当前操作人信息 $userInfo = (new UserService())->getUserInfo($request->user->uid, ''); if (empty($userInfo['user']['id']) || empty($userInfo['user']['name'])) { throw new BusinessException('无法获取当前用户信息'); } $handlerId = (int)$userInfo['user']['id']; $handlerName = (string)$userInfo['user']['name']; $data['handler_id'] = $handlerId; $data['handler_name'] = $handlerName; // 确认状态:1=通过,2=拒绝 (按你现在用法) $status = isset($data['status']) ? (int)$data['status'] : null; if (!in_array($status, [1, 2], true)) { throw new BusinessException('非法的归还处理状态'); } // 2. 构建查询配置,定位到这条归还记录 $config = $this->buildQueryConfig($request, $scene); // 更新时不允许前端覆盖主键 unset($data['requisition_item_return_id']); // 3. 获取现有记录 /** @var \app\model\InvRequisitionItemReturn|null $existingRecord */ $existingRecord = $this->logicShow($config); if (!$existingRecord) { throw new BusinessException('未找到对应的归还申请记录'); } // 已处理校验:只要归还记录的 status 已经是「通过或拒绝」,就不能再处理 $existingStatus = (int)($existingRecord->status ?? 0); if (in_array($existingStatus, [1, 2], true) || !empty($existingRecord->handler_id)) { throw new BusinessException('该归还申请已经被处理过了'); } // 4. 更新归还记录(状态 + 审核人 + 备注等) $updateResult = $this->logicUpdateBy($config, $data); $resultItem = $this->logicShow($config); InvRequisitionReturn::where('id',$resultItem['return_id'])->update(['status' => $data['status']]); foreach ($attachment as $attac) { Db::table('inv_requisition_return_approve_attachment')->insertGetId([ 'requisition_id' => $resultItem['requisition_id'], 'return_id' => $resultItem['return_id'], 'file_name' => $attac['original_name'], 'file_path' => $attac['file_path'], 'created_at' => time(), ]); } if ($updateResult === 0) { throw new BusinessException('更新归还申请失败,可能记录不存在或已被处理'); } //InvRequisitionReturn::where('return_id', $data['id'])->update(['status' => $status]); // 5. 若是拒绝,直接返回(不改库存、不改已归还数量) if ($status === 2) { // 拒绝:这里你返回 true 或者返回更新后的记录都可以 return true; } // 6. 审核通过:继续处理领用明细 & 库存 & 领用单状态 // 注意:requisition_item_id、item_id、return_qty 等字段在本次确认流程中不会被修改, // 所以可以直接使用 $existingRecord 中的值(都是只读字段) $requisitionItemId = (int)$existingRecord->requisition_item_id; $itemId = (int)$existingRecord->item_id; $returnQty = (int)$existingRecord->return_qty; $requisitionId = (int)$existingRecord->requisition_id; if ($requisitionItemId <= 0 || $itemId <= 0 || $returnQty <= 0 || $requisitionId <= 0) { throw new BusinessException('归还申请记录缺少必要字段,无法处理'); } // 6.1 处理领用明细 /** @var \app\model\InvRequisitionItem|null $item */ $item = InvRequisitionItem::find($requisitionItemId); if (!$item) { throw new BusinessException('未找到对应的领用明细记录'); } $approvedQty = (int)($item->approved_qty ?? 0); $returnedQty = (int)($item->returned_qty ?? 0); if ($approvedQty <= 0) { throw new BusinessException('该物品尚未审批出库,无法归还'); } if ($returnedQty + $returnQty > $approvedQty) { throw new BusinessException('归还数量超过已借数量,无法确认'); } $item->returned_qty = $returnedQty + $returnQty; if ($item->returned_qty >= $approvedQty) { $item->is_finished = 1; } $item->updated_at = time(); if ($item->save() === false) { throw new BusinessException('更新领用明细失败'); } // 6.2 处理库存(入库) (new InvItemService())->stockIn($itemId, $returnQty); // 6.3 检查整张单是否已全部归还(只看非耗材) $notFinishedCount = InvRequisitionItem::query() ->where('requisition_id', $requisitionId) ->where('is_consumable_snap', 0) ->where('is_finished', 0) ->count(); if ($notFinishedCount === 0) { // 全部非耗材都已归还 $affected = InvRequisition::where('id', $requisitionId)->update([ 'is_returned' => 1, 'status' => 17, 'updated_at' => time(), ]); // Eloquent::update 返回的是影响行数,0 表示未更新到任何行 if ($affected === 0) { throw new BusinessException('更新领用单状态失败,可能记录不存在'); } }else{ // 非耗材未全部归还 $affected = InvRequisition::where('id', $requisitionId)->update([ 'status' => 16, 'updated_at' => time(), ]); } // 7. 返回处理结果(保持你原来的 postProcess 习惯) return $this->postProcess(ucfirst($scene), $existingRecord); }); } catch (BusinessException $e) { throw $e; } catch (\Throwable $e) { throw new BusinessException("确认归还物品失败:" . $e->getMessage()); } } /** * 物品领用归还确认 * * @param Request $request * @param array $data * @param string $scene * @return array|bool * @throws BusinessException */ public function serviceReturnConfirm(Request $request, array $data, string $scene = 'returnConfirm') { try { if(empty($data['return_id'])) { $data['return_id'] = $data['requisition_item_return_id']; } $attachment = $request->post('attachment') ?? []; unset($data['attachment']); // 归还确认是业务顶层方法,这里统一使用事务 return Db::transaction(function () use ($request, $data, $scene, $attachment) { // 1. 当前操作人信息 $userInfo = (new UserService())->getUserInfo($request->user->uid, ''); if (empty($userInfo['user']['id']) || empty($userInfo['user']['name'])) { throw new BusinessException('无法获取当前用户信息'); } $handlerId = (int)$userInfo['user']['id']; $handlerName = (string)$userInfo['user']['name']; $data['handler_id'] = $handlerId; $data['handler_name'] = $handlerName; // 确认状态:1=通过,2=拒绝 $status = isset($data['status']) ? (int)$data['status'] : null; if (!in_array($status, [1, 2], true)) { throw new BusinessException('非法的归还处理状态'); } // 2. 构建查询配置,定位到这条归还记录 $returnService = new InvRequisitionReturnService(); $return = InvRequisitionReturn::where('id', $data['return_id'])->first(); if (!$return) { throw new BusinessException('未找到对应的领用归还记录'); } $requisitionId = (int)$return->requisition_id; $existingStatus = (int)($return->status ?? 0); if (in_array($existingStatus, [1, 2], true)) { throw new BusinessException('该归还申请已经被处理过了'); } $return->status = $status; $return->save(); if($status == 1){ //更改库存 $returnItem = Db::table('inv_requisition_item_return') ->where('return_id', $return->id) ->get(); foreach ($returnItem as $item) { //$invItem = InvItem::where('id', $item->item_id)->first(); (new InvItemService())->stockIn($item->item_id,$item->return_qty,['change_type'=>InvStockLog::CHANGE_TYPE_IN_RETURN, 'biz_type'=>InvStockLog::BIZ_TYPE_RETURN,]); } } // 保存归还审核附件 foreach ($attachment as $attac) { Db::table('inv_requisition_return_approve_attachment')->insertGetId([ 'requisition_id' => $requisitionId, 'return_id' => $data['return_id'], 'file_name' => $attac['original_name'], 'file_path' => $attac['file_path'], 'created_at' => time(), ]); } //查询归还明细 $itemReturns = InvRequisitionItemReturn::where('return_id', $data['return_id'])->get(); foreach ($itemReturns as $itemReturn) { $itemReturn->status = $status; $itemReturn->handle_remark = $data['handle_remark']; $itemReturn->save(); // 5. 若是拒绝,直接跳出(不改库存、不改已归还数量) if ($status === 2) { // 拒绝:这里你返回 true 或者返回更新后的记录都可以 continue; } // 6. 审核通过:继续处理领用明细 & 库存 & 领用单状态 // 注意:requisition_item_id、item_id、return_qty 等字段在本次确认流程中不会被修改, // 所以可以直接使用 $existingRecord 中的值(都是只读字段) $requisitionItemId = (int)$itemReturn->requisition_item_id; $itemId = (int)$itemReturn->item_id; $returnQty = (int)$itemReturn->return_qty; if ($requisitionItemId <= 0 || $itemId <= 0 || $returnQty <= 0 || $requisitionId <= 0) { throw new BusinessException('归还申请记录缺少必要字段,无法处理'); } // 6.2 处理领用明细 /** @var \app\model\InvRequisitionItem|null $item */ $item = InvRequisitionItem::find($requisitionItemId); if (!$item) { throw new BusinessException('未找到对应的领用明细记录'); } $approvedQty = (int)($item->approved_qty ?? 0); $returnedQty = (int)($item->returned_qty ?? 0); if ($approvedQty <= 0) { throw new BusinessException('该物品尚未审批出库,无法归还'); } if ($returnedQty + $returnQty > $approvedQty) { throw new BusinessException('归还数量超过已借数量,无法确认'); } $item->returned_qty = $returnedQty + $returnQty; if ($item->returned_qty >= $approvedQty) { $item->is_finished = 1; } $item->updated_at = time(); if ($item->save() === false) { throw new BusinessException('更新领用明细失败'); } // 6.3 处理库存(入库) (new InvItemService())->stockIn($itemId, $returnQty); } // 5. 若是拒绝,直接返回(不修改领用申请状态) if ($status === 2) { // 拒绝:这里你返回 true 或者返回更新后的记录都可以 return $return; } // 6.3 检查整张单是否已全部归还(只看非耗材) $notFinishedCount = InvRequisitionItem::query() ->where('requisition_id', $requisitionId) ->where('is_consumable_snap', 0) ->where('is_finished', 0) ->count(); if ($notFinishedCount === 0) { // 全部非耗材都已归还 $affected = InvRequisition::where('id', $requisitionId)->update([ 'is_returned' => 1, 'status' => 17, 'updated_at' => time(), ]); // Eloquent::update 返回的是影响行数,0 表示未更新到任何行 if ($affected === 0) { throw new BusinessException('更新领用单状态失败,可能记录不存在'); } }else{ // 非耗材未全部归还 $affected = InvRequisition::where('id', $requisitionId)->update([ 'status' => 16, 'updated_at' => time(), ]); } // 7. 返回处理结果(保持你原来的 postProcess 习惯) //return $this->postProcess(ucfirst($scene), $existingRecord); return $return; }); } catch (BusinessException $e) { throw $e; } catch (\Throwable $e) { throw new BusinessException("确认归还物品失败:" . $e->getMessage()); } } /** * 构建查询配置 * * @param Request $request * @param string $scene * @return array */ protected function buildQueryConfig(Request $request, string $scene = 'default'): array { $p = $request->all(); $config = []; $config['model'] = $this->modelClass; switch ($scene) { case 'returnConfirm': $config['where']['id'] = $p['requisition_item_return_id']; break; case 'index': // if(isset($p['status']) && $p['status']!=''){ // $config['where']['status'] = $p['status']; // } // if(isset($p['handler_id']) && $p['handler_id']=='self'){ // $config['where']['applicant_id'] = $request->user->uid; // }else{ // $config['where']['handler_id'] = $request->user->uid; // } // $config['order']['id'] = 'desc'; break; case 'show': $config['where']['id'] = $p['id']; break; case 'store': break; case 'update': $config['where']['id'] = $p['id']; break; case 'destroy': $config['where']['id'] = $p['id']; break; } return $config; } /** 分页列表 */ public function serviceList(Request $request, string $scene = 'index') { $InvRequisitionReturn = new InvRequisitionReturnService(); return $InvRequisitionReturn->serviceList($request, $scene); } /** 查看单条 */ public function serviceShow(Request $request, string $scene = 'show') { $id = $request->get('id'); $return = InvRequisitionReturn::where('id', $id)->first(); $return['item_return'] = InvRequisitionItemReturn::where('return_id', $return['id'])->orderBy('id', 'desc')->get(); return $return; } /** * 生成申请单号 * * @return string */ private function generateOrderNo(): string { return 'RR' . date('Ymd') . strtoupper(substr(uniqid(), -6)); } }