validator; } /** * 构造 CrudUtil 查询配置 */ protected function buildQueryConfig(Request $request, string $scene = 'default'): array { $p = $request->all(); $config = []; $config['model'] = $this->modelClass; switch ($scene) { case 'default': $config['where']['status'] = 1; $config['order']['sort'] = 'desc'; $config['order']['id'] = 'desc'; break; case 'index': if(isset($p['status']) && $p['status']){ $config['where']['status'] = $p['status']; } if (isset($p['applicant_id']) && $p['applicant_id'] == 'self') { //自己的领用申请 $config['where']['applicant_id'] = $request->user->uid; } elseif (isset($p['applicant_id']) && $p['applicant_id']) { //指定人员的申请 $config['where']['applicant_id'] = $p['applicant_id']; } /* else { //审批人对应的记录 $config['with'] = ['approval']; $config['where']['approval.approver_id'] = $request->user->uid; //$config['where']['approval.action'] = 0; }*/ if(isset($p['phase']) && $p['phase'] == 'approve'){//借用审批中 $config['where']['status'] = ['in',[10,11]]; if(isset($p['scene']) && $p['scene'] == 'approval'){ $config['with'] = ['approval']; $config['where']['approval.action'] = 0; } }elseif(isset($p['phase']) && $p['phase'] == 'repaid'){//已全部归还 $config['where']['status'] = 17; }elseif(isset($p['phase']) && $p['phase'] == 'unreturned'){ //未归还 $config['where']['status'] = ['in',[11,12,13,14,15,16,18,19]]; } //审核列表 elseif(isset($p['phase']) && $p['phase'] == 'pending'){ //待审核 $config['where']['status'] = ['in',[10,11]]; $config['with'] = ['approval']; $config['where']['approval.approver_id'] = $request->user->uid; $config['where']['approval.action'] = 0; }elseif(isset($p['phase']) && $p['phase'] == 'approved'){ //已通过 $config['where']['status'] = ['in',[11,12,14,15,16,17,18,19]]; $config['with'] = ['approval']; $config['where']['approval.approver_id'] = $request->user->uid; $config['where']['approval.action'] = [">",0]; }elseif(isset($p['phase']) && $p['phase'] == 'refuse'){ //拒绝,未通过 $config['where']['status'] = ['in',[13]]; } $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; } /** * 创建物品申请 前置 * * @param array $data * @param Request $request * @return array */ public function beforeServiceStore($data, Request $request): array { $user = (new UserService())->getUserInfo($request->user->uid, ''); if (empty($user['user']['id']) || empty($user['user']['name'])) { throw new BusinessException('无法获取当前用户信息'); } $data['order_no'] = $this->generateOrderNo(); $data['applicant_id'] = (int)$user['user']['id']; $data['applicant_name'] = (string)$user['user']['name']; $data['leader_id'] = $user['user']['report_to']; $data['status'] = 10; //查询物品管理人是否是一个 if(count($data['items'])>=2){ $manage_user = []; foreach ($data['items'] as $item) { $manage_user[] = InvItem::where('id',$item['item_id'])->value('manage_user_id'); } $manage_user = array_unique($manage_user); if(count($manage_user)>1){ throw new BusinessException("物品管理员不是同一人"); } } return $data; } /** * 处理物品申请 * * @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); $created = $this->logicCreate($data, $dl); // 处理物品申请项 $this->processRequisitionItems($created, $data['items']); // 处理物品申请附件 $this->processRequisitionAttachmentItems($created, $data['attachment']); $res = $this->postProcess('Store', $created); $manage_user_id = InvItem::where('id',$data['items'][0]['item_id'])->value('manage_user_id'); $approval = [ 'id' => $res['id'], //申请单id 'applicant_id' => $res['applicant_id'], //申请人id 'manage_user_id' => $manage_user_id, ]; $this->startApproval($approval); return $res; }); } catch (\Throwable $e) { throw new BusinessException("创建物品申请失败:".$e->getMessage()); } } /** * 启动审批 */ public function startApproval($requisition) { // 启动审批 //$appro = new \app\common\utils\approval\service\ApprovalFlowService(); $appro = new \app\common\service\InvRequisitionApproveService(); $formData = [ 'manage_user_id' => $requisition['manage_user_id'], ]; return $appro->start('inv_requisition', 'inv_requisition', $requisition['id'], $formData, $requisition['applicant_id']); } /** * 更新物品申请 * * @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'); // 获取申请项/附件 $items = $data['items']; $attachment = $data['attachment']; unset($data['items']); unset($data['attachment']); // 执行更新操作 $this->logicUpdateBy($config, $data); // 获取更新后的完整记录 $updated = $this->logicShow($config); // 重新创建新的申请项 $this->processRequisitionItems((object)$updated, $items, true); // 重新创建新的附件 $this->processRequisitionAttachmentItems((object)$updated, $attachment, true); return $this->postProcess('Update', $updated); }); } catch (\Throwable $e) { throw new BusinessException("更新物品申请失败:" . $e->getMessage()."|".$e->getLine()."|".$e->getFile()); } } /** * 处理物品申请项 * * @param Model $created * @param array $items * @return void */ private function processRequisitionItems(Model $created, array $items, bool $isUpdate = false): void { if ($isUpdate) { $this->clearRequisitionItems($created->id); } foreach ($items as $item) { $invitem = InvItem::where('id',$item)->first(); $itemData = [ 'requisition_id' => $created->id, 'item_id' => $item['item_id'], 'item_name_snap' => $item['item_name_snap'], // 修改:使用 snap 字段 'spec_model_snap' => $item['spec_model_snap']??'', // 修改:使用 snap 字段 'unit_snap' => $item['unit_snap']??'', // 修改:使用 snap 字段 'is_consumable_snap' => $item['is_consumable_snap']?? ($invitem['is_consumable_snap']??0), // 修改:使用 snap 字段 'request_qty' => (int) $item['request_qty'] ?? 1, // 动态获取数量,转为整型 'approved_qty' => (int) $item['request_qty'] ?? 1, // 初始审批数量等于申请数量 'remark' => $item['remark'] ?? '' ]; InvRequisitionItem::create($itemData); } } /** * 处理物品申请项 * * @param Model $created * @param array $items * @return void */ private function processRequisitionAttachmentItems(Model $created, array $attachment, bool $isUpdate = false): void { if ($isUpdate) { $this->clearRequisitionAttachmentItems($created->id); } foreach ($attachment as $file) { $attachmentData = [ 'requisition_id' => $created->id, 'file_name' => $file['original_name'], 'file_path' => $file['file_path'] ]; InvRequisitionAttachment::create($attachmentData); } } /** * 删除申请项 * * @param int $requisitionId * @return void */ public function clearRequisitionItems(int $requisitionId): void { InvRequisitionItem::where('requisition_id', $requisitionId)->delete(); } /** * 删除申请项 * * @param int $requisitionId * @return void */ public function clearRequisitionAttachmentItems(int $requisitionId): void { InvRequisitionAttachment::where('requisition_id', $requisitionId)->delete(); } /** * 生成申请单号 * * @return string */ private function generateOrderNo(): string { return 'RQ' . date('Ymd') . strtoupper(substr(uniqid(), -6)); } /** * 扫码领取物品 * * @param Request $request * @param array $data * @return array * @throws BusinessException */ public function qrClaim(Request $request, array $data): array { return Db::transaction(function () use ($request, $data) { $itemId = (int)($data['item_id'] ?? 0); $qty = (int)($data['qty'] ?? 1); if ($itemId <= 0) { throw new BusinessException('物品ID无效'); } if ($qty <= 0) { throw new BusinessException('领取数量必须大于0'); } // 获取物品信息 $item = InvItem::find($itemId); if (!$item) { throw new BusinessException('物品不存在'); } // 检查是否允许扫码领取 if (!$item->allow_qr_claim) { throw new BusinessException('该物品不允许扫码领取'); } // 获取用户信息 $user = (new UserService())->getUserInfo($request->user->uid, ''); $applicantId = (int)$user['user']['id']; $applicantName = (string)$user['user']['name']; // 检查扫码领取数量限制 if ($item->qr_code_num > 0) { // 查询该用户已扫码领取该物品的数量 $claimedQty = InvRequisitionItem::whereHas('requisition', function ($q) use ($applicantId) { $q->where('applicant_id', $applicantId) ->where('claim_type', InvRequisition::CLAIM_TYPE_QR); })->where('item_id', $itemId) ->sum('approved_qty'); if (($claimedQty + $qty) > $item->qr_code_num) { throw new BusinessException("扫码领取数量超限,每人最多可领取 {$item->qr_code_num} {$item->unit},已领取 {$claimedQty} {$item->unit}"); } } // 检查库存 if ($item->stock_qty < $qty) { throw new BusinessException('库存不足,当前库存: ' . $item->stock_qty); } // 创建领用申请记录(直接完成) $requisition = InvRequisition::create([ 'order_no' => $this->generateOrderNo(), 'applicant_company_id' => $user['user']['company_id'] ?? 0, 'dept_id' => $user['user']['dept_id'] ?? 0, 'applicant_id' => $applicantId, 'applicant_name' => $applicantName, 'leader_id' => $user['user']['report_to'] ?? 0, 'manager_id' => $item->manage_user_id, 'borrow_reason' => '扫码领取', 'use_start_date' => date('Y-m-d', time()), 'use_end_date' => date('Y-m-d', time()), 'status' => 19, // 已审批(无需归还) 'claim_type' => InvRequisition::CLAIM_TYPE_QR, 'qr_claim_time' => time(), 'remark' => $data['remark'] ?? '扫码领取', ]); // 创建领用明细 InvRequisitionItem::create([ 'requisition_id' => $requisition->id, 'item_id' => $itemId, 'item_name_snap' => $item->item_name, 'spec_model_snap' => $item->spec_model, 'unit_snap' => $item->unit, 'is_consumable_snap' => $item->is_consumable, 'request_qty' => $qty, 'approved_qty' => $qty, 'remark' => '扫码领取', ]); // 扣减库存 (new InvItemService())->stockOut($itemId, $qty, false, [ 'biz_type' => InvStockLog::BIZ_TYPE_QR_CLAIM, 'biz_no' => $requisition->order_no, 'operator_id' => $applicantId, 'operator_name' => $applicantName, 'remark' => '扫码领取', ]); return $requisition->toArray(); }); } /** * 管理员代领(支持多人) * * @param Request $request * @param array $data * @return array * @throws BusinessException */ public function proxyClaim(Request $request, array $data): array { return Db::transaction(function () use ($request, $data) { $itemId = (int)($data['item_id'] ?? 0); $qty = (int)($data['qty'] ?? 1); $targetUserIds = $data['target_user_id']; if ($itemId <= 0) { throw new BusinessException('物品ID无效'); } if ($qty <= 0) { throw new BusinessException('领取数量必须大于0'); } if (empty($targetUserIds)) { throw new BusinessException('请选择代领用户'); } // 获取物品信息 $item = InvItem::find($itemId); if (!$item) { throw new BusinessException('物品不存在'); } $targetUserIds = explode(',', $targetUserIds); // 获取操作人(管理员)信息 $proxyUser = (new UserService())->getUserInfo($request->user->uid, ''); $proxyUserId = (int)$proxyUser['user']['id']; $proxyUserName = (string)$proxyUser['user']['name']; // 验证操作人是否是物品管理员 if ($item->manage_user_id != $proxyUserId) { throw new BusinessException('您不是该物品的管理员,无权代领'); } // 计算总需求量 $totalQty = $qty * count($targetUserIds); // 检查库存 if ($item->stock_qty < $totalQty) { throw new BusinessException('库存不足,当前库存: ' . $item->stock_qty . ',需要: ' . $totalQty); } $createdRequisitions = []; $remark = $data['remark'] ?? '管理员代领'; // 为每个目标用户创建领用记录 foreach ($targetUserIds as $targetUserId) { $targetUserId = (int)$targetUserId; if ($targetUserId <= 0) { continue; } // 获取目标用户信息 $targetUser = (new UserService())->getUserInfo($targetUserId, ''); if (!$targetUser['user']) { throw new BusinessException('用户ID ' . $targetUserId . ' 不存在'); } // 创建领用申请记录(直接完成) $requisition = InvRequisition::create([ 'order_no' => $this->generateOrderNo(), 'applicant_company_id' => $targetUser['user']['company_id'] ?? 0, 'dept_id' => $targetUser['user']['dept_id'] ?? 0, 'applicant_id' => $targetUserId, 'applicant_name' => $targetUser['user']['name'], 'leader_id' => $targetUser['user']['report_to'] ?? 0, 'manager_id' => $item->manage_user_id, 'borrow_reason' => $data['borrow_reason'] ?? '管理员代领', 'use_start_date' => date('Y-m-d', time()), 'use_end_date' => date('Y-m-d', time()), 'status' => 19, // 已审批(无需归还) 'claim_type' => InvRequisition::CLAIM_TYPE_PROXY, 'proxy_claim_user_id' => $proxyUserId, 'proxy_claim_user_name' => $proxyUserName, 'remark' => $remark, ]); // 创建领用明细 InvRequisitionItem::create([ 'requisition_id' => $requisition->id, 'item_id' => $itemId, 'item_name_snap' => $item->item_name, 'spec_model_snap' => $item->spec_model, 'unit_snap' => $item->unit, 'is_consumable_snap' => $item->is_consumable, 'request_qty' => $qty, 'approved_qty' => $qty, 'remark' => $remark, ]); $createdRequisitions[] = $requisition->toArray(); } // 一次性扣减总库存 (new InvItemService())->stockOut($itemId, $totalQty, false, [ 'biz_type' => InvStockLog::BIZ_TYPE_PROXY_CLAIM, 'biz_no' => 'PROXY_' . time(), 'operator_id' => $proxyUserId, 'operator_name' => $proxyUserName, 'remark' => '管理员代领(' . count($targetUserIds) . '人×' . $qty . ')', ]); return [ 'created_count' => count($createdRequisitions), 'requisitions' => $createdRequisitions, 'total_qty' => $totalQty, ]; }); } /** * 查询扫码/代领记录列表 * * @param Request $request * @return array */ public function qrClaimsList(Request $request): array { $query = InvRequisition::query()->with(['items']); // 只查询扫码领取和代领记录 $query->whereIn('claim_type', [ InvRequisition::CLAIM_TYPE_QR, InvRequisition::CLAIM_TYPE_PROXY ]); // 按申请人筛选(移动端"我的记录"功能) if ($request->get('applicant_id') === 'self') { $query->where('applicant_id', $request->user->uid); } elseif (!empty($request->get('applicant_id'))) { $query->where('applicant_id', $request->get('applicant_id')); } // 按领取方式筛选 if (!empty($request->get('claim_type'))) { $query->where('claim_type', $request->get('claim_type')); } // 按代领操作人筛选 if (!empty($request->get('proxy_user_id'))) { $query->where('proxy_claim_user_id', $request->get('proxy_user_id')); } // 按物品ID筛选 if (!empty($request->get('item_id'))) { $query->whereHas('items', function ($q) use ($request) { $q->where('item_id', $request->get('item_id')); }); } // 时间范围筛选 if (!empty($request->get('start_time'))) { $query->where('created_at', '>=', strtotime($request->get('start_time'))); } if (!empty($request->get('end_time'))) { $query->where('created_at', '<=', strtotime($request->get('end_time'))); } // 排序 $query->orderBy('id', 'desc'); // 分页 $perPage = $request->get('size', 20); $page = $request->get('current', 1); $result = $query->paginate($perPage, ['*'], 'page', $page); return [ 'records' => $result->getCollection()->map(function ($req) { return [ 'id' => $req->id, 'order_no' => $req->order_no, 'applicant_id' => $req->applicant_id, 'applicant_name' => $req->applicant_name, 'claim_type' => $req->claim_type, 'claim_type_text' => InvRequisition::getClaimTypeText($req->claim_type), 'qr_claim_time' => $req->qr_claim_time ? date('Y-m-d H:i:s', $req->qr_claim_time) : null, 'proxy_claim_user_id' => $req->proxy_claim_user_id, 'proxy_claim_user_name' => $req->proxy_claim_user_name, 'status' => $req->status, 'borrow_reason' => $req->borrow_reason, 'remark' => $req->remark, 'created_at' => $req->created_at ? $req->created_at->format('Y-m-d H:i:s') : null, 'items' => $req->items->map(function ($item) { return [ 'item_id' => $item->item_id, 'item_name_snap' => $item->item_name_snap, 'approved_qty' => $item->approved_qty, ]; })->toArray(), ]; })->toArray(), 'total' => $result->total(), 'current' => $result->currentPage(), 'size' => $result->perPage(), ]; } }