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; } $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 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['requisition_id'] = $data['requisition_id']; $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已拒绝 ); $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' // ] // ] // ]; $data['return_at'] = time(); return $data; } /** * 更新物品归还申请 * * @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']); $this->logicUpdateBy($config, $data); // 获取更新后的完整记录 $updated = $this->logicShow($config); // 处理附件更新 $this->processRequisitionItemReturnAttachment($updated, $attachment, true); return $this->postProcess('Update', $updated); }); } catch (\Throwable $e) { throw new BusinessException("更新物品归还申请失败:" . $e->getMessage()); } } /** * 处理物品申请附件 * * @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 serviceReturnConfirm(Request $request, array $data, string $scene = 'returnConfirm') { try { // 归还确认是业务顶层方法,这里统一使用事务 return Db::transaction(function () use ($request, $data, $scene) { // 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); if ($updateResult === 0) { throw new BusinessException('更新归还申请失败,可能记录不存在或已被处理'); } // 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 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'])){ $config['where']['status'] = $p['status']; } $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; } /** * 生成申请单号 * * @return string */ private function generateOrderNo(): string { return 'RR' . date('Ymd') . strtoupper(substr(uniqid(), -6)); } }