Files
fdesk/app/common/service/InvItemService.php
T

320 lines
10 KiB
PHP
Raw Normal View History

2025-12-05 09:22:53 +00:00
<?php
namespace app\common\service;
2026-01-05 10:42:08 +08:00
use app\common\logic\InvItemLogic;
2026-02-24 13:55:48 +08:00
use app\common\model\User;
2025-12-05 09:22:53 +00:00
use app\common\validate\InvItemValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InvItem;
2026-02-24 13:55:48 +08:00
use support\Redis;
2025-12-05 09:22:53 +00:00
use support\Request;
2025-12-09 07:33:04 +00:00
use support\exception\BusinessException;
2025-12-05 09:22:53 +00:00
class InvItemService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InvItem::class;
/**
* 验证器
*/
protected $validator = InvItemValidate::class;
2026-01-05 10:42:08 +08:00
protected $logic;
public function __construct()
{
$this->logic = (new InvItemLogic());
}
2025-12-05 09:22:53 +00:00
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
2026-01-05 10:42:08 +08:00
/**
* 构造 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;
}
2025-12-05 09:22:53 +00:00
/**
* 生成 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'] = '件';
2026-01-07 09:42:08 +08:00
$data['stock_qty'] = $data['stock_qty']??1;
2025-12-05 09:22:53 +00:00
$data['location'] = '';
2026-02-24 13:55:48 +08:00
Redis::set('inv_store_u_stock', $request->user->uid, 'EX', 30);
2025-12-05 09:22:53 +00:00
return $data;
}
2026-02-24 13:55:48 +08:00
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']);
}
2025-12-09 07:33:04 +00:00
2026-02-24 16:15:49 +08:00
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();
2026-02-25 13:57:05 +08:00
$change_qty = $data['stock_qty'] - $item['stock_qty'];
2026-02-24 16:15:49 +08:00
// 记录库存变化日志
$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);
2026-02-25 14:46:34 +08:00
return $data;
2026-02-24 16:15:49 +08:00
}
2025-12-09 07:33:04 +00:00
/**
* 变动物品库存数量
*
* @param int $itemId 物品ID
* @param int $changeQty 变动数量:>0 入库,<0 出库
* @param bool $allowNegative 是否允许库存变成负数,默认不允许
2026-02-24 11:28:12 +08:00
* @param array $options 额外选项(用于记录日志)
2026-02-25 13:13:36 +08:00
* - change_type: 变动类型
2026-02-24 11:28:12 +08:00
* - biz_type: 业务类型
* - biz_no: 单据号
* - operator_id: 操作人ID
* - operator_name: 操作人姓名
* - remark: 备注
2025-12-09 07:33:04 +00:00
* @return InvItem 最新的物品记录
* @throws BusinessException
*/
2026-02-24 11:28:12 +08:00
public function changeStock(int $itemId, int $changeQty, bool $allowNegative = false, array $options = []): InvItem
2025-12-09 07:33:04 +00:00
{
if ($changeQty === 0) {
throw new BusinessException('库存变动数量不能为 0');
}
2026-02-24 11:28:12 +08:00
// 获取变动前的库存(用于日志)
$itemBefore = InvItem::query()->find($itemId);
if (!$itemBefore) {
throw new BusinessException('物品不存在');
}
$beforeQty = $itemBefore->stock_qty;
2025-12-09 07:33:04 +00:00
$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);
2026-02-24 11:28:12 +08:00
// 记录库存变化日志
$this->recordStockLog([
'item_id' => $itemId,
'item_name_snap' => $item->item_name,
'change_qty' => $changeQty,
'before_qty' => $beforeQty,
'after_qty' => $item->stock_qty,
2026-02-25 13:13:36 +08:00
'change_type' => $options['change_type'] ?? null,
2026-02-24 11:28:12 +08:00
'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);
2025-12-09 07:33:04 +00:00
return $item;
}
/**
* 出库(减库存,默认不允许负数)
2026-02-24 11:28:12 +08:00
*
* @param int $itemId 物品ID
* @param int $qty 出库数量
* @param bool $allowNegative 是否允许负数
* @param array $options 日志选项(同 changeStock)
* @return InvItem
* @throws BusinessException
2025-12-09 07:33:04 +00:00
*/
2026-02-24 11:28:12 +08:00
public function stockOut(int $itemId, int $qty, bool $allowNegative = false, array $options = []): InvItem
2025-12-09 07:33:04 +00:00
{
if ($qty <= 0) {
throw new BusinessException('出库数量必须大于 0');
}
2026-02-24 11:28:12 +08:00
// 确定变动类型: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'] ?? '领用出库',
]));
2025-12-09 07:33:04 +00:00
}
/**
* 入库(加库存)
2026-02-24 11:28:12 +08:00
*
* @param int $itemId 物品ID
* @param int $qty 入库数量
* @param array $options 日志选项(同 changeStock)
* @return InvItem
* @throws BusinessException
2025-12-09 07:33:04 +00:00
*/
2026-02-24 11:28:12 +08:00
public function stockIn(int $itemId, int $qty, array $options = []): InvItem
2025-12-09 07:33:04 +00:00
{
if ($qty <= 0) {
throw new BusinessException('入库数量必须大于 0');
}
2026-02-24 11:28:12 +08:00
// 确定变动类型: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());
}
2025-12-09 07:33:04 +00:00
}
2026-01-05 10:42:08 +08:00
/**
* 获取车型list
*/
public function getList()
{
return $this->logic->getList();
}
2025-12-05 09:22:53 +00:00
}