Initial commit: queue workspace
Made-with: Cursor
This commit is contained in:
398
pro_v3.5.1/app/services/product/stock/StockInventoryServices.php
Normal file
398
pro_v3.5.1/app/services/product/stock/StockInventoryServices.php
Normal file
@@ -0,0 +1,398 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\services\product\stock;
|
||||
|
||||
use app\dao\product\stock\StockInventoryDao;
|
||||
use app\dao\product\stock\StockRecordDao;
|
||||
use app\dao\product\stock\StockRecordItemDao;
|
||||
use app\model\product\stock\StockRecord;
|
||||
use app\services\BaseServices;
|
||||
use app\services\order\StoreOrderRefundServices;
|
||||
use app\services\order\StoreOrderServices;
|
||||
use app\services\product\product\StoreProductServices;
|
||||
use app\services\product\sku\StoreProductAttrValueServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\services\FormBuilder as Form;
|
||||
use think\annotation\Inject;
|
||||
use think\exception\ValidateException;
|
||||
use think\facade\Db;
|
||||
use think\facade\Route as Url;
|
||||
|
||||
/**
|
||||
* 库存管理服务类
|
||||
* Class StockRecordServices
|
||||
* @package app\services\stock
|
||||
* @mixin StockInventoryDao
|
||||
*/
|
||||
class StockInventoryServices extends BaseServices
|
||||
{
|
||||
/**
|
||||
* @var StockInventoryDao
|
||||
*/
|
||||
#[Inject]
|
||||
protected StockInventoryDao $dao;
|
||||
|
||||
/**
|
||||
* 获取出入库记录列表
|
||||
* @param array $where
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getStockRecordList(array $where, array $with = [], $limits = 0): array
|
||||
{
|
||||
[$page, $limit] = $this->getPageValue();
|
||||
$limit = $limits ?: $limit;
|
||||
$with = is_array($with) ? array_merge($with, ['admin']) : ['admin'];
|
||||
$list = $this->dao->getList($where, '*', $page, $limit, $with);
|
||||
$count = $this->dao->count($where);
|
||||
return compact('list', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建
|
||||
* @param array $data
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createStockRecord(array $data, int $adminId, $id = 0): bool
|
||||
{
|
||||
// 验证数据
|
||||
$this->validateStockRecordData($data);
|
||||
|
||||
return $this->transaction(function () use ($data, $adminId, $id) {
|
||||
$id = $this->saveStockInventoryRecord($data, $adminId, $id);
|
||||
|
||||
$itemDao = app()->make(StockRecordItemDao::class);
|
||||
if ($id) {
|
||||
$itemDao->delete(['record_id' => $id, 'type' => 2]);
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$good_stock = $good_inventory_stock = $defective_stock = $defective_inventory_stock = 0;
|
||||
$out_library_items = [];
|
||||
$enter_library_items = [];
|
||||
|
||||
$storeProductServices = app()->make(StoreProductServices::class);
|
||||
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
|
||||
$product_ids = array_unique(array_column($data['product'], 'product_id'));
|
||||
$uniques = array_unique(array_column($data['product'], 'unique'));
|
||||
$product_list = $storeProductServices->search([])->whereIn('id', $product_ids)->column('id,store_name,code', 'id');
|
||||
$attr_list = $storeProductAttrValueServices->search([])->whereIn('unique', $uniques)->column('unique,suk,image,bar_code', 'unique');
|
||||
|
||||
foreach ($data['product'] as $item) {
|
||||
$item['product_name'] = $product_list[$item['product_id']]['store_name'] ?? '';
|
||||
$item['product_code'] = $product_list[$item['product_id']]['code'] ?? '';
|
||||
$item['product_image'] = $attr_list[$item['unique']]['image'] ?? '';
|
||||
$item['product_suk'] = $attr_list[$item['unique']]['suk'] ?? '';
|
||||
$item['product_bar_code'] = $attr_list[$item['unique']]['bar_code'] ?? '';
|
||||
$this->processProductItem($item, $out_library_items, $enter_library_items, $good_stock, $good_inventory_stock, $defective_stock, $defective_inventory_stock, $items, $id);
|
||||
}
|
||||
|
||||
$this->dao->update($id, [
|
||||
'good_stock' => $good_stock,
|
||||
'good_inventory_stock' => $good_inventory_stock,
|
||||
'defective_stock' => $defective_stock,
|
||||
'defective_inventory_stock' => $defective_inventory_stock
|
||||
]);
|
||||
|
||||
if (!$itemDao->saveAllItems($items)) {
|
||||
throw new AdminException('创建商品详情失败');
|
||||
}
|
||||
if ($data['status'] == 1) {
|
||||
$stockRecordServices = app()->make(StockRecordServices::class);
|
||||
if (!empty($out_library_items)) {
|
||||
$out_library = [
|
||||
'type' => StockRecord::TYPE_OUT,
|
||||
'stock_type' => StockRecord::STOCK_TYPE_LOSS,
|
||||
'record_date' => time(),
|
||||
'product' => $out_library_items,
|
||||
'remark' => '盘点亏损'
|
||||
];
|
||||
$stockRecordServices->createStockRecord($out_library, $adminId);
|
||||
}
|
||||
if (!empty($enter_library_items)) {
|
||||
$enter_library = [
|
||||
'type' => StockRecord::TYPE_IN,
|
||||
'stock_type' => StockRecord::STOCK_TYPE_PROFIT,
|
||||
'record_date' => time(),
|
||||
'product' => $enter_library_items,
|
||||
'remark' => '盘点报溢'
|
||||
];
|
||||
$stockRecordServices->createStockRecord($enter_library, $adminId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建盘点记录
|
||||
* @param array $data
|
||||
* @param int $adminId
|
||||
* @param $id
|
||||
* @return mixed
|
||||
*/
|
||||
private function saveStockInventoryRecord(array $data, int $adminId, $id): mixed
|
||||
{
|
||||
if ($id) {
|
||||
$info = $this->dao->get($id);
|
||||
if (!$info) {
|
||||
throw new AdminException('盘点记录不存在');
|
||||
}
|
||||
$info->remark = $data['remark'] ?? '';
|
||||
$info->operator_id = $adminId;
|
||||
$info->status = $data['status'];
|
||||
$info->save();
|
||||
} else {
|
||||
// 生成单号
|
||||
$recordNo = app()->make(StockRecordServices::class)->generateUniqueRecordNo(3);
|
||||
// 创建主记录
|
||||
$recordData = [
|
||||
'record_no' => $recordNo,
|
||||
'remark' => $data['remark'] ?? '',
|
||||
'operator_id' => $adminId,
|
||||
'status' => $data['status'],
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
];
|
||||
$record = $this->dao->save($recordData);
|
||||
if (!$record) {
|
||||
throw new AdminException('创建盘点记录失败');
|
||||
}
|
||||
$id = $record->id;
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理商品项
|
||||
* @param array $item
|
||||
* @param array $out_library_items
|
||||
* @param array $enter_library_items
|
||||
* @param $good_stock
|
||||
* @param $good_inventory_stock
|
||||
* @param $defective_stock
|
||||
* @param $defective_inventory_stock
|
||||
* @param array $items
|
||||
* @param int $id
|
||||
* @return void
|
||||
* User: liusl
|
||||
* DateTime: 2025/9/22 15:49
|
||||
*/
|
||||
private function processProductItem(array $item, array &$out_library_items, array &$enter_library_items, &$good_stock, &$good_inventory_stock, &$defective_stock, &$defective_inventory_stock, array &$items, int $id)
|
||||
{
|
||||
$poor_good_stock = $poor_defective_stock = 0;
|
||||
if ($item['good_inventory_stock'] !== null) {
|
||||
$poor_good_stock = bcsub($item['good_inventory_stock'], $item['good_stock'], 0);
|
||||
}
|
||||
if ($item['defective_inventory_stock'] !== null) {
|
||||
$poor_defective_stock = bcsub($item['defective_inventory_stock'], $item['defective_stock'], 0);
|
||||
}
|
||||
|
||||
$_enter_library_items = [];
|
||||
$_out_library_items = [];
|
||||
if ($poor_good_stock > 0) {
|
||||
$good_stock += (int)$poor_good_stock;
|
||||
$_enter_library_items = [
|
||||
'product_id' => $item['product_id'],
|
||||
'unique' => $item['unique'] ?? 0,
|
||||
'good_stock' => $poor_good_stock,
|
||||
'defective_stock' => 0,
|
||||
];
|
||||
} elseif ($poor_good_stock < 0) {
|
||||
$good_inventory_stock += (int)$poor_good_stock;
|
||||
$_out_library_items = [
|
||||
'product_id' => $item['product_id'],
|
||||
'unique' => $item['unique'] ?? 0,
|
||||
'good_stock' => bcmul($poor_good_stock, -1, 0),
|
||||
'defective_stock' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if ($poor_defective_stock > 0) {
|
||||
$defective_stock += (int)$poor_defective_stock;
|
||||
|
||||
if ($_enter_library_items) {
|
||||
$_enter_library_items['defective_stock'] = $poor_defective_stock;
|
||||
} else {
|
||||
$_enter_library_items = [
|
||||
'product_id' => $item['product_id'],
|
||||
'unique' => $item['unique'] ?? 0,
|
||||
'good_stock' => 0,
|
||||
'defective_stock' => $poor_defective_stock,
|
||||
];
|
||||
}
|
||||
|
||||
} elseif ($poor_defective_stock < 0) {
|
||||
$defective_inventory_stock += (int)$poor_defective_stock;
|
||||
|
||||
if ($_out_library_items) {
|
||||
$_out_library_items['defective_stock'] = bcmul($poor_defective_stock, -1, 0);
|
||||
} else {
|
||||
$_out_library_items = [
|
||||
'product_id' => $item['product_id'],
|
||||
'unique' => $item['unique'] ?? 0,
|
||||
'good_stock' => 0,
|
||||
'defective_stock' => bcmul($poor_defective_stock, -1, 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
if (count($_out_library_items) > 0) {
|
||||
$out_library_items[] = $_out_library_items;
|
||||
}
|
||||
if (count($_enter_library_items) > 0) {
|
||||
$enter_library_items[] = $_enter_library_items;
|
||||
}
|
||||
|
||||
|
||||
$items[] = [
|
||||
'type' => 2,
|
||||
'record_id' => $id,
|
||||
'product_id' => $item['product_id'],
|
||||
'unique' => $item['unique'] ?? 0,
|
||||
'good_stock' => $item['good_inventory_stock'] !== null ? ($item['good_stock'] ?? 0) : 0,
|
||||
'good_inventory_stock' => $item['good_inventory_stock'] ?? 0,
|
||||
'defective_stock' => $item['defective_inventory_stock'] !== null ? ($item['defective_stock'] ?? 0) : 0,
|
||||
'defective_inventory_stock' => $item['defective_inventory_stock'] ?? 0,
|
||||
'product_name' => $item['product_name'] ?? '',
|
||||
'product_image' => $item['product_image'] ?? '',
|
||||
'product_code' => $item['product_code'] ?? '',
|
||||
'product_suk' => $item['product_suk'] ?? '',
|
||||
'product_bar_code' => $item['product_bar_code'] ?? '',
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核出入库记录
|
||||
* @param int $id
|
||||
* @param int $status
|
||||
* @param string $remark
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function auditStockRecord(int $id, int $status, string $remark = ''): bool
|
||||
{
|
||||
$record = $this->dao->get($id);
|
||||
if (!$record) {
|
||||
throw new AdminException('盘点记录不存在');
|
||||
}
|
||||
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 更新记录状态
|
||||
$updateData = [
|
||||
'status' => $status,
|
||||
'audit_remark' => $remark,
|
||||
'audit_time' => time(),
|
||||
'update_time' => time(),
|
||||
];
|
||||
|
||||
if (!$this->dao->update($id, $updateData)) {
|
||||
throw new AdminException('更新记录状态失败');
|
||||
}
|
||||
|
||||
|
||||
Db::commit();
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证出入库记录数据
|
||||
* @param array $data
|
||||
* @throws AdminException
|
||||
*/
|
||||
protected function validateStockRecordData(array $data): void
|
||||
{
|
||||
|
||||
if (empty($data['product']) || !is_array($data['product'])) {
|
||||
throw new AdminException('商品信息不能为空');
|
||||
}
|
||||
|
||||
foreach ($data['product'] as $item) {
|
||||
if (empty($item['product_id'])) {
|
||||
throw new AdminException('商品ID不能为空');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param $id
|
||||
* @return array
|
||||
* @throws \ReflectionException
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* User: liusl
|
||||
* DateTime: 2025/9/17 16:50
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取盘点详情
|
||||
* @param int $id 盘点记录ID
|
||||
* @return array
|
||||
* @throws \ReflectionException
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function read($id)
|
||||
{
|
||||
$info = $this->dao->get($id, ['*'], ['admin']);
|
||||
if (!$info) {
|
||||
throw new AdminException('记录不存在');
|
||||
}
|
||||
$info = $info->toArray();
|
||||
|
||||
$product = app()->make(StockRecordItemServices::class)->search(['record_id' => $id, 'type' => 2])->select()->toArray();
|
||||
if (!is_array($product)) {
|
||||
$product = [];
|
||||
}
|
||||
$info['product'] = $product;
|
||||
return $info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 备注表单
|
||||
* @param $id
|
||||
* @return mixed
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* User: liusl
|
||||
* DateTime: 2025/9/18 14:42
|
||||
*/
|
||||
public function remarkFrom($id)
|
||||
{
|
||||
$info = $this->dao->get($id);
|
||||
if (!$info) {
|
||||
throw new AdminException('记录不存在');
|
||||
}
|
||||
$field[] = Form::textarea('remark', '备注', $info['remark'] ?? '');
|
||||
return create_form('添加备注', $field, Url::buildUrl('/stock/inventory/remark_save/' . $id), 'POST');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\services\product\stock;
|
||||
|
||||
use app\dao\product\stock\StockRecordItemDao;
|
||||
use app\services\BaseServices;
|
||||
use think\annotation\Inject;
|
||||
|
||||
/**
|
||||
* 库存管理服务类
|
||||
* Class StockRecordServices
|
||||
* @package app\services\stock
|
||||
* @mixin StockRecordItemDao
|
||||
*/
|
||||
class StockRecordItemServices extends BaseServices
|
||||
{
|
||||
/**
|
||||
* @var StockRecordItemDao
|
||||
*/
|
||||
#[Inject]
|
||||
protected StockRecordItemDao $dao;
|
||||
|
||||
}
|
||||
862
pro_v3.5.1/app/services/product/stock/StockRecordServices.php
Normal file
862
pro_v3.5.1/app/services/product/stock/StockRecordServices.php
Normal file
@@ -0,0 +1,862 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\services\product\stock;
|
||||
|
||||
use app\dao\product\stock\StockRecordDao;
|
||||
use app\dao\product\stock\StockRecordItemDao;
|
||||
use app\model\product\stock\StockRecord;
|
||||
use app\services\BaseServices;
|
||||
use app\services\order\StoreOrderRefundServices;
|
||||
use app\services\order\StoreOrderServices;
|
||||
use app\services\product\product\StoreProductServices;
|
||||
use app\services\product\sku\StoreProductAttrValueServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\services\FormBuilder as Form;
|
||||
use think\annotation\Inject;
|
||||
use think\exception\ValidateException;
|
||||
use think\facade\Db;
|
||||
use think\facade\Route as Url;
|
||||
|
||||
/**
|
||||
* 库存管理服务类
|
||||
* Class StockRecordServices
|
||||
* @package app\services\stock
|
||||
* @mixin StockRecordDao
|
||||
*/
|
||||
class StockRecordServices extends BaseServices
|
||||
{
|
||||
/**
|
||||
* @var StockRecordDao
|
||||
*/
|
||||
#[Inject]
|
||||
protected StockRecordDao $dao;
|
||||
|
||||
/**
|
||||
* 获取出入库记录列表
|
||||
* @param array $where
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getStockRecordList(array $where, $with = [], $limits = 0): array
|
||||
{
|
||||
[$page, $limit] = $this->getPageValue();
|
||||
$limit = $limits ?: $limit;
|
||||
$with = is_array($with) ? array_merge($with, ['admin']) : ['admin'];
|
||||
$unique = $where['unique'] ?? '';
|
||||
if ($unique) {
|
||||
$with[] = 'items';
|
||||
}
|
||||
$list = $this->dao->getList($where, '*', $page, $limit, $with);
|
||||
$count = $this->dao->getCount($where);
|
||||
$_items = [];
|
||||
if ($unique) {
|
||||
$itemsList = array_column($list, 'items');
|
||||
foreach ($itemsList as $v) {
|
||||
foreach ($v as $item) {
|
||||
if ($item && isset($item['unique']) && $item['unique'] == $unique) {
|
||||
if (!isset($_items[$item['record_id']])) {
|
||||
$_items[$item['record_id']] = [
|
||||
'good_stock' => $item['good_stock'],
|
||||
'defective_stock' => $item['defective_stock'],
|
||||
];
|
||||
} else {
|
||||
$_items[$item['record_id']]['good_stock'] += $item['good_stock'];
|
||||
$_items[$item['record_id']]['defective_stock'] += $item['defective_stock'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
foreach ($list as &$item) {
|
||||
if ($unique) {
|
||||
$item['good_stock'] = $_items[$item['id']]['good_stock'] ?? 0;
|
||||
$item['defective_stock'] = $_items[$item['id']]['defective_stock'] ?? 0;
|
||||
}
|
||||
$item['stock_type_name'] = StockRecord::getStockTypeName($item['stock_type']);
|
||||
// unset($item['items']);
|
||||
if ($item['stock_type'] == 'sale') $item['admin_name'] = '用户购买';
|
||||
}
|
||||
|
||||
return compact('list', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建出入库记录
|
||||
* @param array $data
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createStockRecord(array $data, int $adminId, $is_stock = false): bool
|
||||
{
|
||||
// 验证数据
|
||||
$this->validateStockRecordData($data);
|
||||
|
||||
$id = $this->transaction(function () use ($data, $adminId) {
|
||||
// 生成单号
|
||||
$recordNo = $this->generateUniqueRecordNo($data['type']);
|
||||
// 创建主记录
|
||||
$recordData = [
|
||||
'record_no' => $recordNo,
|
||||
'type' => $data['type'],
|
||||
'stock_type' => $data['stock_type'],
|
||||
'after_sale_no' => $data['after_sale_no'] ?? '',
|
||||
'record_date' => is_numeric($data['record_date']) ? $data['record_date'] : strtotime($data['record_date']),
|
||||
'remark' => $data['remark'] ?? '',
|
||||
'operator_id' => $adminId,
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
];
|
||||
$recordId = $this->dao->save($recordData);
|
||||
if (!$recordId) {
|
||||
throw new AdminException('创建出入库记录失败');
|
||||
}
|
||||
|
||||
// 创建商品详情
|
||||
$itemDao = app()->make(StockRecordItemDao::class);
|
||||
$items = [];
|
||||
|
||||
$storeProductServices = app()->make(StoreProductServices::class);
|
||||
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
|
||||
$product_ids = array_unique(array_column($data['product'], 'product_id'));
|
||||
$uniques = array_unique(array_column($data['product'], 'unique'));
|
||||
$product_list = $storeProductServices->search([])->whereIn('id', $product_ids)->column('id,store_name,code', 'id');
|
||||
$attr_list = $storeProductAttrValueServices->search([])->whereIn('unique', $uniques)->column('unique,suk,image,bar_code', 'unique');
|
||||
foreach ($data['product'] as $item) {
|
||||
$stock = $this->calculateStockChanges($data['type'], $data['stock_type'], $item['good_stock'] ?? 0, $item['defective_stock'] ?? 0);
|
||||
$items[] = [
|
||||
'record_id' => $recordId->id,
|
||||
'product_id' => $item['product_id'],
|
||||
'record_date' => $recordData['record_date'],
|
||||
'unique' => $item['unique'] ?? 0,
|
||||
'stock_type' => $data['stock_type'],
|
||||
'good_stock' => $stock['good_stock_change'] ?? 0,
|
||||
'defective_stock' => $stock['defective_stock_change'] ?? 0,
|
||||
'product_name' => $product_list[$item['product_id']]['store_name'] ?? '',
|
||||
'product_code' => $product_list[$item['product_id']]['code'] ?? '',
|
||||
'product_image' => $attr_list[$item['unique']]['image'] ?? '',
|
||||
'product_suk' => $attr_list[$item['unique']]['suk'] ?? '',
|
||||
'product_bar_code' => $attr_list[$item['unique']]['bar_code'] ?? '',
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
if (!$itemDao->saveAllItems($items)) {
|
||||
throw new AdminException('创建商品详情失败');
|
||||
}
|
||||
return $recordId->id;
|
||||
});
|
||||
if (!$is_stock) {
|
||||
event('product.stock.create', [$id]);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据出入库类型计算库存变化量
|
||||
* @param int $recordType 记录类型 1=入库 2=出库
|
||||
* @param string $stockType 库存类型
|
||||
* @param int $goodStock 良品数量
|
||||
* @param int $defectiveStock 残次品数量
|
||||
* @return array
|
||||
*/
|
||||
public function calculateStockChanges(int $recordType, string $stockType, $goodStock, $defectiveStock): array
|
||||
{
|
||||
switch ($stockType) {
|
||||
case StockRecord::STOCK_TYPE_DEFECTIVE_TO_GOOD: // 残次品转良品入库
|
||||
// 良品增加(正数),残次品减少(负数)
|
||||
$goodStockChange = $goodStock;
|
||||
$defectiveStockChange = -$goodStock;
|
||||
break;
|
||||
|
||||
case StockRecord::STOCK_TYPE_GOOD_TO_DEFECTIVE: // 良品转残次品出库
|
||||
// 良品减少(负数),残次品增加(正数)
|
||||
$goodStockChange = -$goodStock;
|
||||
$defectiveStockChange = $goodStock;
|
||||
break;
|
||||
|
||||
default:
|
||||
// 普通入库和出库逻辑
|
||||
if ($recordType == StockRecord::TYPE_IN) {
|
||||
// 入库:良品和残次品都是正数
|
||||
$goodStockChange = $goodStock;
|
||||
$defectiveStockChange = $defectiveStock;
|
||||
} else {
|
||||
// 出库:良品和残次品都是负数
|
||||
$goodStockChange = $goodStock ? -$goodStock : 0;
|
||||
$defectiveStockChange = $defectiveStock ? -$defectiveStock : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'good_stock_change' => $goodStockChange,
|
||||
'defective_stock_change' => $defectiveStockChange
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品出入库明细
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
public function getProductStockDetails(array $where): array
|
||||
{
|
||||
[$page, $limit] = $this->getPageValue();
|
||||
$itemDao = app()->make(StockRecordItemDao::class);
|
||||
|
||||
$list = $itemDao->getProductStockDetails($where, $page, $limit);
|
||||
$count = $itemDao->getProductStockDetailsCount($where);
|
||||
|
||||
return compact('list', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证出入库记录数据
|
||||
* @param array $data
|
||||
* @throws AdminException
|
||||
*/
|
||||
protected
|
||||
function validateStockRecordData(array $data): void
|
||||
{
|
||||
if (empty($data['type']) || !in_array($data['type'], [StockRecord::TYPE_IN, StockRecord::TYPE_OUT])) {
|
||||
throw new AdminException('出入库类型错误');
|
||||
}
|
||||
// 验证stock_type字段
|
||||
if (empty($data['stock_type'])) {
|
||||
throw new AdminException('出入库子类型不能为空');
|
||||
}
|
||||
|
||||
$validStockTypes = [
|
||||
StockRecord::STOCK_TYPE_PURCHASE,
|
||||
StockRecord::STOCK_TYPE_RETURN,
|
||||
StockRecord::STOCK_TYPE_OTHER_IN,
|
||||
StockRecord::STOCK_TYPE_DEFECTIVE_TO_GOOD,
|
||||
StockRecord::STOCK_TYPE_EXPIRED_RETURN,
|
||||
StockRecord::STOCK_TYPE_PROFIT,
|
||||
StockRecord::STOCK_TYPE_USE_OUT,
|
||||
StockRecord::STOCK_TYPE_SCRAP_OUT,
|
||||
StockRecord::STOCK_TYPE_GOOD_TO_DEFECTIVE,
|
||||
StockRecord::STOCK_TYPE_OTHER_OUT,
|
||||
StockRecord::STOCK_TYPE_SALE,
|
||||
StockRecord::STOCK_TYPE_LOSS,
|
||||
];
|
||||
|
||||
if (!in_array($data['stock_type'], $validStockTypes)) {
|
||||
throw new AdminException('出入库子类型无效');
|
||||
}
|
||||
|
||||
if (empty($data['record_date'])) {
|
||||
throw new AdminException('出入库日期不能为空');
|
||||
}
|
||||
|
||||
if (empty($data['product']) || !is_array($data['product'])) {
|
||||
throw new AdminException('商品信息不能为空');
|
||||
}
|
||||
|
||||
foreach ($data['product'] as $item) {
|
||||
if (empty($item['product_id'])) {
|
||||
throw new AdminException('商品ID不能为空');
|
||||
}
|
||||
if(isset($item['good_stock']) && isset($item['defective_stock']) && !$item['good_stock'] && !$item['defective_stock']){
|
||||
throw new AdminException('请填写出入库数量');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一的单号
|
||||
* @param int $type
|
||||
* @return string
|
||||
*/
|
||||
public function generateUniqueRecordNo(int $type): string
|
||||
{
|
||||
$maxAttempts = 10;
|
||||
$attempts = 0;
|
||||
|
||||
do {
|
||||
$recordNo = $this->dao->generateRecordNo($type);
|
||||
$exists = $this->dao->checkRecordNoExists($recordNo);
|
||||
$attempts++;
|
||||
} while ($exists && $attempts < $maxAttempts);
|
||||
|
||||
if ($exists) {
|
||||
throw new AdminException('生成单号失败,请重试');
|
||||
}
|
||||
|
||||
return $recordNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出出入库记录
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
public
|
||||
function exportStockRecords(array $where): array
|
||||
{
|
||||
$list = $this->dao->getList($where, '*', 1, 0, ['items']);
|
||||
|
||||
$exportData = [];
|
||||
foreach ($list as $record) {
|
||||
foreach ($record['items'] as $item) {
|
||||
$exportData[] = [
|
||||
'record_no' => $record['record_no'],
|
||||
'type' => $record['type'] == 1 ? '入库' : '出库',
|
||||
'product_id' => $item['product_id'],
|
||||
'sku_id' => $item['sku_id'],
|
||||
'quantity' => $item['quantity'],
|
||||
'unit_price' => $item['unit_price'],
|
||||
'total_price' => $item['total_price'],
|
||||
'record_date' => date('Y-m-d H:i:s', $record['record_date']),
|
||||
'status' => $this->getStatusText($record['status']),
|
||||
'remark' => $record['remark']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $exportData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param $id
|
||||
* @return array
|
||||
* @throws \ReflectionException
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* User: liusl
|
||||
* DateTime: 2025/9/17 16:50
|
||||
*/
|
||||
public function read($id, $unique = '')
|
||||
{
|
||||
$info = $this->dao->get($id, ['*'], ['admin']);
|
||||
if (!$info) {
|
||||
throw new AdminException('记录不存在');
|
||||
}
|
||||
$info = $info->toArray();
|
||||
$info['stock_type_name'] = StockRecord::getStockTypeName($info['stock_type']);
|
||||
|
||||
$product = app()->make(StockRecordItemServices::class)->search(['record_id' => $id, 'unique' => $unique, 'type' => 1])->select()->toArray();
|
||||
if (!is_array($product)) {
|
||||
$product = [];
|
||||
}
|
||||
foreach ($product as &$item){
|
||||
$item['good_stock'] = abs($item['good_stock']);
|
||||
$item['defective_stock'] = abs($item['defective_stock']);
|
||||
}
|
||||
$info['product'] = $product;
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 备注表单
|
||||
* @param $id
|
||||
* @return mixed
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* User: liusl
|
||||
* DateTime: 2025/9/18 14:42
|
||||
*/
|
||||
public
|
||||
function remarkFrom($id)
|
||||
{
|
||||
$info = $this->dao->get($id);
|
||||
if (!$info) {
|
||||
throw new AdminException('记录不存在');
|
||||
}
|
||||
$field[] = Form::textarea('remark', '备注:', $info['remark'] ?? '');
|
||||
return create_form('添加备注', $field, Url::buildUrl('/stock/record/remark_save/' . $id), 'POST');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取售后单商品信息列表
|
||||
* @param string $order_id
|
||||
* @return mixed
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* User: liusl
|
||||
* DateTime: 2025/9/17 16:15
|
||||
*/
|
||||
public
|
||||
function getRefundList(string $order_id)
|
||||
{
|
||||
$storeOrderRefundServices = app()->make(StoreOrderRefundServices::class);
|
||||
if ($this->dao->get(['after_sale_no' => $order_id, 'type' => 1])) {
|
||||
throw new AdminException('该订单已经入库过了');
|
||||
}
|
||||
$order = $storeOrderRefundServices->get(['order_id' => $order_id], ['*']);
|
||||
if (!$order) throw new ValidateException('订单不存在');
|
||||
$order = $order->toArray();
|
||||
/** @var StoreOrderServices $orderServices */
|
||||
$orderServices = app()->make(StoreOrderServices::class);
|
||||
$orderInfo = $orderServices->get($order['store_order_id'], ['*'], ['invoice', 'virtual']);
|
||||
$orderInfo = $orderInfo->toArray();
|
||||
$orderInfo = $orderServices->tidyOrder($orderInfo, true, true);
|
||||
$cartInfo = $orderInfo['cartInfo'];
|
||||
return $cartInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库存类型定义
|
||||
* @return array
|
||||
*/
|
||||
private function getStockTypes(): array
|
||||
{
|
||||
return [
|
||||
'in' => ['purchase', 'return', 'other_in', 'defective_to_good', 'profit'],
|
||||
'out' => ['expired_return', 'use_out', 'scrap_out', 'good_to_defective', 'other_out', 'sale', 'loss']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建基础查询模型
|
||||
* @param array $where
|
||||
* @return mixed
|
||||
*/
|
||||
private function buildBaseQuery(array $where)
|
||||
{
|
||||
/** @var StockRecordItemDao $stockRecordItemDao */
|
||||
$stockRecordItemDao = app()->make(StockRecordItemDao::class);
|
||||
|
||||
/** @var StoreProductServices $productServices */
|
||||
$productServices = app()->make(StoreProductServices::class);
|
||||
|
||||
// 构建基础查询
|
||||
$model = $stockRecordItemDao->search(['type' => 1, 'keywords' => $where['product_name'] ?? '']);
|
||||
|
||||
$where['record_date'] = $where['record_date'] && is_string($where['record_date']) ? explode('-', $where['record_date']) : [];
|
||||
if (count($where['record_date']) == 2 && $where['record_date'][0] && $where['record_date'][1]) {
|
||||
$startTime = strtotime($where['record_date'][0]);
|
||||
$endTime = strtotime($where['record_date'][1]) + 86400 - 1;
|
||||
$model = $model->whereBetween('record_date', [$startTime, $endTime]);
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库存类型名称映射
|
||||
* @return array
|
||||
*/
|
||||
private function getStockTypeNames(): array
|
||||
{
|
||||
return [
|
||||
'purchase' => '采购入库',
|
||||
'return' => '退货入库',
|
||||
'other_in' => '其他入库',
|
||||
'defective_to_good' => '残次品转良',
|
||||
'expired_return' => '过期退货',
|
||||
'profit' => '盘盈入库',
|
||||
'use_out' => '试用出库',
|
||||
'scrap_out' => '报废出库',
|
||||
'good_to_defective' => '良品转残次品',
|
||||
'other_out' => '其他出库',
|
||||
'sale' => '销售出库',
|
||||
'loss' => '盘亏出库'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出入库统计数据列表
|
||||
* @param array $where
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getStockStatisticsList(array $where): array
|
||||
{
|
||||
[$page, $limit] = $this->getPageValue();
|
||||
|
||||
/** @var StoreProductServices $productServices */
|
||||
$productServices = app()->make(StoreProductServices::class);
|
||||
|
||||
/** @var StoreProductAttrValueServices $attrValueServices */
|
||||
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
|
||||
|
||||
// 获取库存类型定义
|
||||
$stockTypes = $this->getStockTypes();
|
||||
$inStockTypes = $stockTypes['in'];
|
||||
$outStockTypes = $stockTypes['out'];
|
||||
|
||||
// 构建基础查询
|
||||
$model = $this->buildBaseQuery($where);
|
||||
if ($model === null) {
|
||||
return ['list' => [], 'count' => 0];
|
||||
}
|
||||
|
||||
// 根据stock_type参数筛选入库或出库
|
||||
if (isset($where['stock_type']) && $where['stock_type'] !== '') {
|
||||
if ($where['stock_type'] == 1) {
|
||||
// 查询入库统计
|
||||
$model = $model->whereIn('stock_type', $inStockTypes);
|
||||
} elseif ($where['stock_type'] == 2) {
|
||||
// 查询出库统计
|
||||
$model = $model->whereIn('stock_type', $outStockTypes);
|
||||
}
|
||||
}
|
||||
|
||||
// 按商品规格唯一值分组统计,分别统计各种类型的数量
|
||||
$fieldArray = [
|
||||
'product_id',
|
||||
'product_suk',
|
||||
'product_name',
|
||||
'product_image',
|
||||
'product_bar_code',
|
||||
'unique',
|
||||
'SUM(CASE
|
||||
WHEN stock_type = "defective_to_good" THEN good_stock
|
||||
WHEN stock_type = "good_to_defective" THEN defective_stock
|
||||
ELSE good_stock + defective_stock
|
||||
END) as total_stock',
|
||||
'product_bar_code'
|
||||
];
|
||||
|
||||
// 根据查询类型添加对应的统计字段
|
||||
if ($where['stock_type'] == 1) {
|
||||
// 入库统计
|
||||
foreach ($inStockTypes as $type) {
|
||||
if ($type === 'defective_to_good') {
|
||||
$fieldArray[] = "SUM(CASE WHEN stock_type = '{$type}' THEN good_stock ELSE 0 END) as {$type}_stock";
|
||||
} else {
|
||||
$fieldArray[] = "SUM(CASE WHEN stock_type = '{$type}' THEN good_stock + defective_stock ELSE 0 END) as {$type}_stock";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 出库统计
|
||||
foreach ($outStockTypes as $type) {
|
||||
if ($type === 'good_to_defective') {
|
||||
$fieldArray[] = "SUM(CASE WHEN stock_type = '{$type}' THEN defective_stock ELSE 0 END) as {$type}_stock";
|
||||
} else {
|
||||
$fieldArray[] = "SUM(CASE WHEN stock_type = '{$type}' THEN good_stock + defective_stock ELSE 0 END) as {$type}_stock";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$statistics = $model->field($fieldArray)
|
||||
->group('product_id, unique')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 获取总数 - 重新构建查询
|
||||
$countModel = $this->buildBaseQuery($where);
|
||||
if ($countModel === null) {
|
||||
return ['list' => [], 'count' => 0];
|
||||
}
|
||||
|
||||
if (isset($where['stock_type']) && $where['stock_type'] !== '') {
|
||||
if ($where['stock_type'] == 1) {
|
||||
$countModel = $countModel->whereIn('stock_type', $inStockTypes);
|
||||
} elseif ($where['stock_type'] == 2) {
|
||||
$countModel = $countModel->whereIn('stock_type', $outStockTypes);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分组后的总数
|
||||
$countResult = $countModel->field('product_id, unique')->group('product_id, unique')->select();
|
||||
$count = count($countResult);
|
||||
|
||||
// 获取类型名称映射
|
||||
$typeNames = $this->getStockTypeNames();
|
||||
|
||||
// 组装返回数据
|
||||
$list = [];
|
||||
foreach ($statistics as $item) {
|
||||
|
||||
$result = [
|
||||
'product_id' => $item['product_id'],
|
||||
'product_name' => $item['product_name'],
|
||||
'unique' => $item['unique'],
|
||||
'bar_code' => $item['product_bar_code'] ?? '',
|
||||
'sku_name' => $item['product_suk'],
|
||||
'total_stock' => $item['total_stock']
|
||||
];
|
||||
$_result[] = [
|
||||
'prop' => abs((int)$item['total_stock']),
|
||||
'label' => '总库存'
|
||||
];
|
||||
// 添加各种类型的统计数据
|
||||
$allTypes = array_merge($inStockTypes, $outStockTypes);
|
||||
foreach ($allTypes as $type) {
|
||||
$stockKey = $type . '_stock';
|
||||
if (isset($item[$stockKey])) {
|
||||
$_result[] = [
|
||||
'prop' => abs((int)$item[$stockKey]),
|
||||
'label' => $typeNames[$type]
|
||||
];
|
||||
}
|
||||
}
|
||||
$result['result'] = $_result;
|
||||
$list[] = $result;
|
||||
}
|
||||
|
||||
return compact('list', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出入库整体统计数据
|
||||
* @param array $where
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getStockOverallStatistics(array $where): array
|
||||
{
|
||||
// 获取库存类型定义
|
||||
$stockTypes = $this->getStockTypes();
|
||||
$inStockTypes = $stockTypes['in'];
|
||||
$outStockTypes = $stockTypes['out'];
|
||||
|
||||
// 构建基础查询
|
||||
$model = $this->buildBaseQuery($where);
|
||||
if ($model === null) {
|
||||
return $this->getEmptyStatisticsCards($where);
|
||||
}
|
||||
|
||||
// 根据stock_type参数筛选入库或出库
|
||||
if (isset($where['stock_type']) && $where['stock_type'] !== '') {
|
||||
if ($where['stock_type'] == 1) {
|
||||
// 只查询入库统计
|
||||
$model = $model->whereIn('stock_type', $inStockTypes);
|
||||
} elseif ($where['stock_type'] == 2) {
|
||||
// 只查询出库统计
|
||||
$model = $model->whereIn('stock_type', $outStockTypes);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据查询类型构建不同的统计字段
|
||||
if (isset($where['stock_type']) && $where['stock_type'] == 1) {
|
||||
// 只统计入库
|
||||
$fieldArray = [
|
||||
'SUM(CASE
|
||||
WHEN stock_type = "defective_to_good" THEN good_stock
|
||||
WHEN stock_type IN ("' . implode('","', array_diff($inStockTypes, ['defective_to_good'])) . '") THEN good_stock + defective_stock
|
||||
ELSE 0
|
||||
END) as total_in_stock',
|
||||
'SUM(CASE WHEN stock_type = "purchase" THEN good_stock + defective_stock ELSE 0 END) as purchase_stock',
|
||||
'SUM(CASE WHEN stock_type = "profit" THEN good_stock + defective_stock ELSE 0 END) as profit_stock',
|
||||
'SUM(CASE WHEN stock_type = "return" THEN good_stock + defective_stock ELSE 0 END) as return_stock',
|
||||
'SUM(CASE WHEN stock_type = "other_in" THEN good_stock + defective_stock ELSE 0 END) as other_in_stock',
|
||||
'SUM(CASE WHEN stock_type = "defective_to_good" THEN good_stock ELSE 0 END) as defective_to_good_stock'
|
||||
];
|
||||
} else {
|
||||
// 只统计出库
|
||||
$fieldArray = [
|
||||
'SUM(CASE
|
||||
WHEN stock_type = "good_to_defective" THEN defective_stock
|
||||
WHEN stock_type IN ("' . implode('","', array_diff($outStockTypes, ['good_to_defective'])) . '") THEN good_stock + defective_stock
|
||||
ELSE 0
|
||||
END) as total_out_stock',
|
||||
'SUM(CASE WHEN stock_type = "sale" THEN good_stock + defective_stock ELSE 0 END) as sale_stock',
|
||||
'SUM(CASE WHEN stock_type = "loss" THEN good_stock + defective_stock ELSE 0 END) as loss_stock',
|
||||
'SUM(CASE WHEN stock_type = "expired_return" THEN good_stock + defective_stock ELSE 0 END) as expired_return_stock',
|
||||
'SUM(CASE WHEN stock_type = "use_out" THEN good_stock + defective_stock ELSE 0 END) as use_out_stock',
|
||||
'SUM(CASE WHEN stock_type = "scrap_out" THEN good_stock + defective_stock ELSE 0 END) as scrap_out_stock',
|
||||
'SUM(CASE WHEN stock_type = "other_out" THEN good_stock + defective_stock ELSE 0 END) as other_out_stock',
|
||||
'SUM(CASE WHEN stock_type = "good_to_defective" THEN defective_stock ELSE 0 END) as good_to_defective_stock'
|
||||
];
|
||||
}
|
||||
|
||||
// 执行统计查询
|
||||
$statistics = $model->field($fieldArray)->find();
|
||||
|
||||
if (!$statistics) {
|
||||
return $this->getEmptyStatisticsCards($where);
|
||||
}
|
||||
|
||||
// 转换为数组
|
||||
$stats = $statistics->toArray();
|
||||
|
||||
// 根据查询类型组装返回数据
|
||||
if (isset($where['stock_type']) && $where['stock_type'] == 1) {
|
||||
// 只返回入库统计卡片
|
||||
return [
|
||||
['name' => '总入库数', 'type' => 1, 'field' => '件', 'count' => (int)$stats['total_in_stock'], 'className' => 'icondingdanjine', 'col' => 8],
|
||||
['name' => '采购入库', 'type' => 1, 'field' => '件', 'count' => (int)$stats['purchase_stock'], 'className' => 'iconshouyintai-shouyin1', 'col' => 8],
|
||||
['name' => '盘盈入库', 'type' => 1, 'field' => '件', 'count' => (int)$stats['profit_stock'], 'className' => 'iconhexiaodingdanjine', 'col' => 8],
|
||||
['name' => '退货入库', 'type' => 1, 'field' => '件', 'count' => (int)$stats['return_stock'], 'className' => 'iconshouhou_tuikuan', 'col' => 8],
|
||||
['name' => '其他入库', 'type' => 1, 'field' => '件', 'count' => (int)$stats['other_in_stock'], 'className' => 'icontuikuandingdanliang', 'col' => 8],
|
||||
['name' => '残次品转良品', 'type' => 1, 'field' => '件', 'count' => (int)$stats['defective_to_good_stock'], 'className' => 'iconfenpeidingdanjine', 'col' => 8],
|
||||
];
|
||||
} else {
|
||||
// 只返回出库统计卡片
|
||||
return [
|
||||
['name' => '总出库数', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['total_out_stock']), 'className' => 'icondingdanjine', 'col' => 6],
|
||||
['name' => '销售出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['sale_stock']), 'className' => 'iconzaishoushangpin', 'col' => 6],
|
||||
['name' => '盘亏出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['loss_stock']), 'className' => 'icondaishenhe-shequneirong', 'col' => 6],
|
||||
['name' => '过期退货', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['expired_return_stock']), 'className' => 'iconshouyintai-tuihuo1', 'col' => 6],
|
||||
['name' => '试用出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['use_out_stock']), 'className' => 'iconshouhou-tuikuan-lv', 'col' => 6],
|
||||
['name' => '报废出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['scrap_out_stock']), 'className' => 'iconjingjiekucun', 'col' => 6],
|
||||
['name' => '其他出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['other_out_stock']), 'className' => 'icontuikuandingdanliang', 'col' => 6],
|
||||
['name' => '良品转残次品', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['good_to_defective_stock']), 'className' => 'iconfenpeidingdanjine', 'col' => 6],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取空的统计数据卡片
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
private function getEmptyStatisticsCards(array $where = []): array
|
||||
{
|
||||
// 根据查询类型返回对应的空数据卡片
|
||||
if (isset($where['stock_type']) && $where['stock_type'] == 1) {
|
||||
// 只返回入库空数据卡片
|
||||
return [
|
||||
['name' => '总入库数', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'icondingdanjine', 'col' => 8],
|
||||
['name' => '采购入库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconshouyintai-shouyin1', 'col' => 8],
|
||||
['name' => '盘盈入库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconhexiaodingdanjine', 'col' => 8],
|
||||
['name' => '退货入库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconshouhou_tuikuan', 'col' => 8],
|
||||
['name' => '其他入库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'icontuikuandingdanliang', 'col' => 8],
|
||||
['name' => '残次品转良品', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconfenpeidingdanjine', 'col' => 8]
|
||||
];
|
||||
} else {
|
||||
// 只返回出库空数据卡片
|
||||
return [
|
||||
['name' => '总出库数', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconchuku', 'col' => 6],
|
||||
['name' => '销售出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconzaishoushangpin', 'col' => 6],
|
||||
['name' => '盘亏出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'icondaishenhe-shequneirong', 'col' => 6],
|
||||
['name' => '过期退货', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconshouyintai-tuihuo1', 'col' => 6],
|
||||
['name' => '试用出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconshouhou-tuikuan-lv', 'col' => 6],
|
||||
['name' => '报废出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconjingjiekucun', 'col' => 6],
|
||||
['name' => '其他出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'icontuikuandingdanliang', 'col' => 6],
|
||||
['name' => '良品转残次品', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconfenpeidingdanjine', 'col' => 6]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 明细导出查询
|
||||
* @param array $where
|
||||
* @param $with
|
||||
* @param int $limits
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* User: liusl
|
||||
* DateTime: 2025/10/11 09:38
|
||||
*/
|
||||
public function getStockDetailsList(array $where, int $limits = 0): array
|
||||
{
|
||||
[$page, $limit] = $this->getPageValue();
|
||||
$limit = $limits ?: $limit;
|
||||
|
||||
$list = $this->dao->getList($where, '*', $page, $limit, ['admin']);
|
||||
$ids = array_column($list, 'id');
|
||||
$items = app()->make(StockRecordItemServices::class)->search(['record_id' => $ids, 'unique' => $where['unique']])->select()->toArray();
|
||||
$_items = [];
|
||||
foreach ($items as $item) {
|
||||
$_items[$item['record_id']][] = $item;
|
||||
}
|
||||
foreach ($list as &$item) {
|
||||
$item['items'] = $_items[$item['id']] ?? [];
|
||||
$item['stock_type_name'] = StockRecord::getStockTypeName($item['stock_type']);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库存统计概览数据
|
||||
* @param array $where 查询条件
|
||||
* @return array
|
||||
*/
|
||||
public function getProductStatistics($where = [])
|
||||
{
|
||||
// 获取当前库存数量(不受时间筛选影响)
|
||||
$currentStock = $this->getCurrentStock($where);
|
||||
//警戒库存商品
|
||||
$store_stock = sys_config('store_stock', 0);
|
||||
$productIds = app()->make(StoreProductServices::class)->search(['status' => 5])->column('id');
|
||||
$attrStock = app()->make(StoreProductAttrValueServices::class)->search(['product_id' => $productIds])->where('stock', '<=', $store_stock)->sum('stock');
|
||||
return [
|
||||
['name' => '良品库存', 'field' => '件', 'count' => $currentStock['good_stock'] ?? 0, 'className' => 'ios-cube', 'col' => 8],
|
||||
// ['name' => '良品入库数量', 'field' => '件', 'count' => $stockMovement['good_in_stock'] ?? 0, 'className' => 'ios-arrow-down', 'col' => 8],
|
||||
// ['name' => '良品出库数量', 'field' => '件', 'count' => $stockMovement['good_out_stock'] ?? 0, 'className' => 'ios-arrow-up', 'col' => 8],
|
||||
['name' => '残次品库存', 'field' => '件', 'count' => $currentStock['defective_stock'] ?? 0, 'className' => 'ios-arrow-round-down', 'col' => 8],
|
||||
['name' => '警戒库存', 'field' => '件', 'count' => $attrStock ?? 0, 'className' => 'ios-warning', 'col' => 8],
|
||||
// ['name' => '残次品出库数量', 'field' => '件', 'count' => $stockMovement['defective_out_stock'] ?? 0, 'className' => 'ios-arrow-round-up', 'col' => 8],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前库存数量(不受时间筛选影响)
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
private function getCurrentStock($where = [])
|
||||
{
|
||||
/** @var StoreProductAttrValueServices $attrValueServices */
|
||||
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
|
||||
|
||||
unset($where['time']);
|
||||
$where['type'] = 0;
|
||||
|
||||
// 查询当前库存
|
||||
$stockData = $attrValueServices->joinAttrSearch($where)
|
||||
->field('SUM(a.stock) as good_stock, SUM(a.defective_stock) as defective_stock')
|
||||
->find();
|
||||
|
||||
return $stockData ? $stockData->toArray() : ['good_stock' => 0, 'defective_stock' => 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出入库统计数量(根据时间筛选)
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
private function getStockMovement($where = [])
|
||||
{
|
||||
/** @var StockRecordItemDao $itemDao */
|
||||
$itemDao = app()->make(StockRecordItemDao::class);
|
||||
$query = $itemDao->search($where);
|
||||
|
||||
|
||||
// 定义入库和出库类型
|
||||
$inStockTypes = ['purchase', 'return', 'other_in', 'defective_to_good', 'profit'];
|
||||
$outStockTypes = ['expired_return', 'use_out', 'scrap_out', 'good_to_defective', 'other_out', 'sale', 'loss'];
|
||||
|
||||
// 统计入库和出库数量
|
||||
$statistics = $query->field([
|
||||
// 良品入库数量
|
||||
'SUM(CASE WHEN stock_type IN ("' . implode('","', $inStockTypes) . '") THEN good_stock ELSE 0 END) as good_in_stock',
|
||||
// 良品出库数量
|
||||
'SUM(CASE WHEN stock_type IN ("' . implode('","', $outStockTypes) . '") THEN good_stock ELSE 0 END) as good_out_stock',
|
||||
// 残次品入库数量
|
||||
'SUM(CASE WHEN stock_type IN ("' . implode('","', $inStockTypes) . '") THEN defective_stock ELSE 0 END) as defective_in_stock',
|
||||
// 残次品出库数量
|
||||
'SUM(CASE WHEN stock_type IN ("' . implode('","', $outStockTypes) . '") THEN defective_stock ELSE 0 END) as defective_out_stock'
|
||||
])->find();
|
||||
|
||||
return $statistics ? $statistics->toArray() : [
|
||||
'good_in_stock' => 0,
|
||||
'good_out_stock' => 0,
|
||||
'defective_in_stock' => 0,
|
||||
'defective_out_stock' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user