logic = (new InvItemLogic()); } /** * 获取验证器 */ public function getValidator() { return new $this->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': $config['where']['status'] = 1; // 关键字 if (isset($p['keyword']) && $p['keyword']) { $k = '%' . $p['keyword'] . '%'; foreach (['item_name'] as $col) { $config['orWhere'][] = [$col, 'like', $k]; } } $config['order']['sort'] = 'desc'; $config['order']['id'] = 'desc'; break; case 'show': $config['where']['id'] = $p['id']; $config['where']['status'] = 1; break; case 'store': break; case 'update': $config['where']['id'] = $p['id']; break; case 'destroy': $config['where']['id'] = $p['id']; break; } return $config; } /** * 生成 8-12 位的物品编码(默认10位) * * @param int $length 编码长度,默认为10(范围 8-12) * @return string 返回大写的字母数字组合编码 */ private function generateItemCode($length = 10) { // 排除易混淆字符:0, O, o, 1, I, l $chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; $code = ''; $maxIndex = strlen($chars) - 1; for ($i = 0; $i < $length; $i++) { $code .= $chars[mt_rand(0, $maxIndex)]; } return $code; } public function beforeServiceStore($data, Request $request): array { $data['item_code'] = $this->generateItemCode(); $data['category_id'] = 0; $data['spec_model'] = ''; $data['unit'] = '件'; $data['stock_qty'] = $data['stock_qty']??1; $data['location'] = ''; Redis::set('inv_store_u_stock', $request->user->uid, 'EX', 30); return $data; } public function afterServiceStore($res) { $uid = redis::get('inv_store_u_stock'); $name = $uid ? User::where('id', $uid)->value('name') : ''; Redis::del('inv_store_u_stock'); // 记录库存变化日志 $this->recordStockLog([ 'item_id' => $res['id'], 'item_name_snap' => $res['item_name'], 'change_qty' => $res['stock_qty'], 'before_qty' => 0, 'after_qty' => $res['stock_qty'], 'biz_type' => 1, 'biz_no' => '', 'operator_id' => $uid, 'operator_name' => $name, 'remark' => '初始数量', ], $res['stock_qty']); } public function beforeServiceUpdate($data, Request $request) { $uid = $request->user->uid; $name = $uid ? User::where('id', $uid)->value('name') : ''; $item = InvItem::where('id', $data['id'])->first(); $change_qty = $data['change_qty'] - $item['stock_qty']; // 记录库存变化日志 $this->recordStockLog([ 'item_id' => $data['id'], 'item_name_snap' => $data['item_name'], 'change_qty' => $change_qty, 'before_qty' => $item['stock_qty'], 'after_qty' => $data['stock_qty'], 'biz_type' => 1, 'biz_no' => '', 'operator_id' => $uid, 'operator_name' => $name, 'remark' => '管理员修改数量', ], $change_qty); } /** * 变动物品库存数量 * * @param int $itemId 物品ID * @param int $changeQty 变动数量:>0 入库,<0 出库 * @param bool $allowNegative 是否允许库存变成负数,默认不允许 * @param array $options 额外选项(用于记录日志) * - change_type: 变动类型 * - biz_type: 业务类型 * - biz_no: 单据号 * - operator_id: 操作人ID * - operator_name: 操作人姓名 * - remark: 备注 * @return InvItem 最新的物品记录 * @throws BusinessException */ public function changeStock(int $itemId, int $changeQty, bool $allowNegative = false, array $options = []): InvItem { if ($changeQty === 0) { throw new BusinessException('库存变动数量不能为 0'); } // 获取变动前的库存(用于日志) $itemBefore = InvItem::query()->find($itemId); if (!$itemBefore) { throw new BusinessException('物品不存在'); } $beforeQty = $itemBefore->stock_qty; $now = time(); // 构建查询 $query = InvItem::query()->where('id', $itemId); // 如果不允许负数,并且是出库场景(变动为负) if (! $allowNegative && $changeQty < 0) { // 要保证:当前 stock_qty >= 出库数量 $query->where('stock_qty', '>=', -$changeQty); } // 使用 increment 实现:UPDATE inv_item SET stock_qty = stock_qty + ?, updated_at = ? WHERE ... $affected = $query->increment('stock_qty', $changeQty, [ 'updated_at' => $now, ]); if ($affected === 0) { // 更新不到行: // 1) 物品不存在 // 2) 或者库存不足(不允许负数时) throw new BusinessException('库存不足,无法完成库存变动'); } // 再查一次最新数据返回(在外层事务中也会保持一致) /** @var InvItem $item */ $item = InvItem::query()->find($itemId); // 记录库存变化日志 $this->recordStockLog([ 'item_id' => $itemId, 'item_name_snap' => $item->item_name, 'change_qty' => $changeQty, 'before_qty' => $beforeQty, 'after_qty' => $item->stock_qty, 'change_type' => $options['change_type'] ?? null, 'biz_type' => $options['biz_type'] ?? null, 'biz_no' => $options['biz_no'] ?? null, 'operator_id' => $options['operator_id'] ?? null, 'operator_name' => $options['operator_name'] ?? null, 'remark' => $options['remark'] ?? null, ], $changeQty); return $item; } /** * 出库(减库存,默认不允许负数) * * @param int $itemId 物品ID * @param int $qty 出库数量 * @param bool $allowNegative 是否允许负数 * @param array $options 日志选项(同 changeStock) * @return InvItem * @throws BusinessException */ public function stockOut(int $itemId, int $qty, bool $allowNegative = false, array $options = []): InvItem { if ($qty <= 0) { throw new BusinessException('出库数量必须大于 0'); } // 确定变动类型:2-出库(领用)或 3-出库(损耗) $changeType = $options['change_type'] ?? \app\common\model\InvStockLog::CHANGE_TYPE_OUT_CLAIM; return $this->changeStock($itemId, -$qty, $allowNegative, array_merge($options, [ 'change_type' => $changeType, 'remark' => $options['remark'] ?? '领用出库', ])); } /** * 入库(加库存) * * @param int $itemId 物品ID * @param int $qty 入库数量 * @param array $options 日志选项(同 changeStock) * @return InvItem * @throws BusinessException */ public function stockIn(int $itemId, int $qty, array $options = []): InvItem { if ($qty <= 0) { throw new BusinessException('入库数量必须大于 0'); } // 确定变动类型:1-入库 或 4-入库(归还) $changeType = $options['change_type'] ?? \app\common\model\InvStockLog::CHANGE_TYPE_IN; return $this->changeStock($itemId, $qty, true, array_merge($options, [ 'change_type' => $changeType, 'remark' => $options['remark'] ?? '入库', ])); } /** * 记录库存变化日志 * * @param array $logData * @param int $changeQty 变动数量 * @return void */ private function recordStockLog(array $logData, int $changeQty): void { try { // 根据变动数量确定变动类型(如果未指定) if (!isset($logData['change_type'])) { if ($changeQty > 0) { $logData['change_type'] = \app\common\model\InvStockLog::CHANGE_TYPE_IN; } else { $logData['change_type'] = \app\common\model\InvStockLog::CHANGE_TYPE_OUT_CLAIM; } } (new InvStockLogService())->record($logData); } catch (\Throwable $e) { // 日志记录失败不影响主业务,仅记录错误 error_log('库存日志记录失败: ' . $e->getMessage()); } } /** * 获取车型list */ public function getList() { return $this->logic->getList(); } }