Files
OMS/app/omeauto/lib/split/storemax.php
2025-12-28 23:13:25 +08:00

586 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Copyright © ShopeX http://www.shopex.cn. All rights reserved.
* See LICENSE file for license details.
*/
/**
* ============================
* @Author: yaokangming
* @Version: 1.0
* @DateTime: 2020/9/10 17:23:35
* @describe: 按库存就全拆
* ============================
*/
class omeauto_split_storemax extends omeauto_split_abstract
{
#拆单规则配置获取数据
/**
* 获取Special
* @return mixed 返回结果
*/
public function getSpecial()
{
if($_POST['from'] == 'split') {
return [];
}
return ['split_type'=>array(
'skucategory' => '按商品品类拆',
'skuweight' => '按商品重量拆',
'skuvolume' => '按商品体积拆',
)];
}
#拆单规则保存前处理
/**
* preSaveSdf
* @param mixed $sdf sdf
* @return mixed 返回值
*/
public function preSaveSdf(&$sdf)
{
if($sdf['split_type'] != 'storemax') {
return [true];
}
if($sdf['split_config']['split_type_2']) {
list($rs, $msg) = kernel::single('omeauto_split_router', $sdf['split_config']['split_type_2'])->preSaveSdf($sdf);
if(!$rs) {
return [false, $msg];
}
if($sdf['split_config']['split_type_2'] == $sdf['split_config']['split_type_3']) {
return [false, '第二层与第三层拆分不能一致'];
}
}
if($sdf['split_config']['split_type_3']) {
list($rs, $msg) = kernel::single('omeauto_split_router', $sdf['split_config']['split_type_3'])->preSaveSdf($sdf);
if(!$rs) {
return [false, $msg];
}
}
return array(true, '保存成功');
}
#拆分订单
/**
* splitOrder
* @param mixed $group group
* @param mixed $splitConfig 配置
* @return mixed 返回值
*/
public function splitOrder(&$group, $splitConfig)
{
$arrOrder = $group->getOrders();
$arrOrderId = array();
$splitOrder = array();
foreach ($arrOrder as $ok => $order) {
$arrOrderId[] = $order['order_id'];
}
list($rs, $msg) = $this->_splitOrderByStore($arrOrder, $group, $splitConfig);
if (!$rs) {
return array(false, $msg);
}
if($splitConfig['split_type_2']) {
list($rs, $msg) = kernel::single('omeauto_split_router', $splitConfig['split_type_2'])->splitOrderFromSplit($arrOrder, $group, $splitConfig);
if(!$rs) {
return [false, $msg];
}
}
if($splitConfig['split_type_3']) {
list($rs, $msg) = kernel::single('omeauto_split_router', $splitConfig['split_type_3'])->splitOrderFromSplit($arrOrder, $group, $splitConfig);
if(!$rs) {
return [false, $msg];
}
}
$splitOrder = $arrOrder;
if ($arrOrderId) {
$group->setSplitOrderId($arrOrderId);
}
$group->updateOrderInfo($splitOrder);
if (empty($splitOrder)) {
return array(false, '无法拆单');
}
return array(true);
}
/**
* splitOrderFromSplit
* @param mixed $arrOrder arrOrder
* @param mixed $group group
* @param mixed $splitConfig 配置
* @return mixed 返回值
*/
public function splitOrderFromSplit(&$arrOrder, &$group, $splitConfig) {
return $this->_splitOrderByStore($arrOrder, $group, $splitConfig);
}
protected function _splitOrderByStore(&$arrOrder, &$group, $splitConfig)
{
$group->setConfirmBranch(true);
$bmIdNum = array();
foreach ($arrOrder as $k => $order) {
foreach ($order['objects'] as $ok => $object) {
foreach ($object['items'] as $ik => $item) {
if($splitConfig['from'] == 'split') {
$bmIdNum[$item['product_id']] += $item['nums'];
continue;
}
if ($item['nums'] > $item['split_num']) {
$arrOrder[$k]['objects'][$ok]['items'][$ik]['original_num'] = $item['nums'];
$arrOrder[$k]['objects'][$ok]['items'][$ik]['nums'] = $nums = $item['nums'] - $item['split_num'];
if ($bmIdNum[$item['product_id']]) {
$bmIdNum[$item['product_id']] += $nums;
} else {
$bmIdNum[$item['product_id']] = $nums;
}
} else {
unset($arrOrder[$k]['objects'][$ok]['items'][$ik]);
}
}
if (empty($arrOrder[$k]['objects'][$ok]['items'])) {
unset($arrOrder[$k]['objects'][$ok]);
}
}
if (empty($arrOrder[$k]['objects'])) {
unset($arrOrder[$k]);
}
}
if (empty($arrOrder)) {
return array(false, '可拆单明细为空,无需拆分');
}
$arrBranchId = $group->getBranchId();
$bmIds = array_keys($bmIdNum);
if (!is_array($arrBranchId)) {
//手工批量审单 固定仓库
$bi = app::get('ome')->model('branch')->db_dump($arrBranchId, 'b_type,b_status');
$arrBranchId = array($arrBranchId);
if ($bi['b_type'] == '2') {
return array(false, '批量审单不支持门店');
} else {
$storeRows = $this->getBranchStoreRows($arrBranchId, $bmIds);
}
} else {
$tidBranchId = array();
foreach ($arrBranchId as $k => $v) {
//选择第一个仓库规则
if(strpos($k, '-')) {
list($tmpTid) = explode('-', $k);
} else {
$tmpTid = 0;
}
if (!isset($tid)) {
$tid = $tmpTid;
$branchConfig = $group->getAutoBranch();
$group->setAutoBranch((array) $branchConfig[$tid]);
}
if ($tid == $tmpTid) {
$tidBranchId[] = $v;
}
}
$storeRows = $this->getStoreRows($tidBranchId, $bmIds, $splitConfig, $arrOrder);
}
if (empty($storeRows)) {
$group->setOrderStatus('*', omeauto_auto_const::__STORE_CODE);
return array(false, '有库存的仓库不存在');
}
//选取sku最全的仓库
$maxBranch = array();
$branchInfo = array();
foreach ($storeRows as $val) {
$validNum = $val['store'] > $val['store_freeze'] ? $val['store'] - $val['store_freeze'] : 0;
if ($validNum) {
$maxBranch[$val['branch_id']] += 1;
$branchInfo[$val['branch_id']]['branch_id'] = $val['branch_id'];
$branchInfo[$val['branch_id']]['store'][$val['product_id']] = $validNum;
}
}
if (empty($maxBranch)) {
$group->setOrderStatus('*', omeauto_auto_const::__STORE_CODE);
return array(false, '有可用库存的仓库不存在');
}
$rs = $this->dealStoreOrder($arrOrder, $branchInfo, $splitConfig);
if(empty($rs['branchInfo'])) {
$group->setOrderStatus('*', omeauto_auto_const::__STORE_CODE);
return [false, '库存不足无法拆单'];
}
$canBranchId = [];
foreach ($rs['is_split'] as $branchId => $value) {
if(!$value) {
$canBranchId[] = $branchId;
}
}
if(!$canBranchId) {
arsort($rs['sku_num']);
$max = current($rs['sku_num']);
foreach ($rs['sku_num'] as $branchId => $num) {
if($max == $num) {
$canBranchId[] = $branchId;
}
}
}
if(count($canBranchId) == 1) {
$branchId = current($canBranchId);
} else {
$group->updateOrderInfo($arrOrder);
$group->setBranchId($canBranchId);
$branchId = kernel::single('omeauto_branch_choose')->getSelectBid($tid, $group, $rs['branchInfo']);
}
$resultBranch = app::get('ome')->model('branch')->db_dump($branchId, 'b_type,b_status');
if ($resultBranch['b_type'] == '2') {
$group->setStoreBranch();
$group->setStoreDlyType('o2o_ship');
}
$logResult = [
'初始值' => $branchInfo,
'拆分情况' => $rs,
'初选仓' => $canBranchId,
'终选仓' => $branchId
];
$this->writeSuccessLog($logResult, $arrOrder);
if(isset($tid)) {#有仓库规则需要重置仓库
$group->setBranchId(array($branchId));
}
$arrOrder = $rs['order'][$branchId];
return [true];
}
#根据库存订单处理拆分
protected function dealStoreOrder($arrOrder, $branchInfo, $splitConfig) {
$return = ['branchInfo'=>[], 'order'=>[], 'sku_num'=>[], 'is_split'=>[]];
if ($splitConfig['dimension'] == '1') {
return $this->dealStoreOrderObject($arrOrder, $branchInfo);
}
foreach ($branchInfo as $branch_id => $value) {
$tmpBI = ['branch_id'=>$branch_id, 'store'=>[]];
$tmpSN = 0;
$is_split = false;
$tmpOrder = $arrOrder;
foreach ($tmpOrder as $k => $order) {
foreach ($order['objects'] as $ok => $object) {
foreach ($object['items'] as $ik => $item) {
if($value['store'][$item['product_id']] < $item['nums']) {
$num = $value['store'][$item['product_id']];
$is_split = true;
} else {
$num = $item['nums'];
}
if($num < 1) {
unset($tmpOrder[$k]['objects'][$ok]['items'][$ik]);
continue;
}
$tmpOrder[$k]['objects'][$ok]['items'][$ik]['nums'] = $num;
$value['store'][$item['product_id']] -= $num;
$tmpBI['store'][$item['product_id']] += $num;
$tmpSN += $num;
}
if(empty($tmpOrder[$k]['objects'][$ok]['items'])) {
unset($tmpOrder[$k]['objects'][$ok]);
}
}
if(empty($tmpOrder[$k]['objects'])) {
unset($tmpOrder[$k]);
}
}
if($tmpOrder) {
$return['branchInfo'][$branch_id] = $tmpBI;
$return['order'][$branch_id] = $tmpOrder;
$return['sku_num'][$branch_id] = $tmpSN;
$return['is_split'][$branch_id] = $is_split;
}
}
return $return;
}
#根据订单库存拆分销售物料
protected function dealStoreOrderObject($arrOrder, $branchInfo) {
$return = ['branchInfo'=>[], 'order'=>[], 'sku_num'=>[], 'is_split'=>[]];
foreach ($arrOrder as $order) {
foreach ($order['objects'] as $object) {
$smIds[$object['goods_id']] = $object['goods_id'];
}
}
$smBc = app::get('material')->model('sales_basic_material')->getList('sm_id, bm_id, number', array('sm_id'=>$smIds));
$smBmNumber = array();
foreach ($smBc as $v) {
$smBmNumber[$v['sm_id']][$v['bm_id']] = $v['number'];
}
foreach ($branchInfo as $branch_id => $value) {
$tmpBI = ['branch_id'=>$branch_id, 'store'=>[]];
$tmpSN = 0;
$is_split = false;
$tmpOrder = $arrOrder;
foreach ($tmpOrder as $k => $order) {
foreach ($order['objects'] as $ok => $object) {
$objNumber = null;
foreach ($object['items'] as $ik => $item) {
$unitNumber = $smBmNumber[$object['goods_id']][$item['product_id']] ? : 1;
if($value['store'][$item['product_id']] < $item['nums']) {
$num = $value['store'][$item['product_id']];
} else {
$num = $item['nums'];
}
$objTmpNum = floor($num / $unitNumber);
if($objTmpNum < 1) {
$objNumber = 0;
break;
}
if(!isset($objNumber) || $objNumber > $objTmpNum) {
$objNumber = $objTmpNum;
}
}
if($objNumber) {
foreach ($object['items'] as $ik => $item) {
$unitNumber = $smBmNumber[$object['goods_id']][$item['product_id']] ? : 1;
$num = intval($objNumber * $unitNumber);
if($num < $item['nums']) {
$is_split = true;
}
$tmpOrder[$k]['objects'][$ok]['items'][$ik]['nums'] = $num;
$value['store'][$item['product_id']] -= $num;
$tmpBI['store'][$item['product_id']] += $num;
$tmpSN += $num;
}
} else {
unset($tmpOrder[$k]['objects'][$ok]);
$is_split = true;
}
}
if(empty($tmpOrder[$k]['objects'])) {
unset($tmpOrder[$k]);
}
}
if($tmpOrder) {
$return['branchInfo'][$branch_id] = $tmpBI;
$return['order'][$branch_id] = $tmpOrder;
$return['sku_num'][$branch_id] = $tmpSN;
$return['is_split'][$branch_id] = $is_split;
}
}
return $return;
}
protected function getStoreRows($arrBranchId, $bmIds, $splitConfig, $arrOrder)
{
switch ($splitConfig['s_type']) {
case '1': #先电商仓再门店仓
$storeRows = $this->getBranchStoreRows($arrBranchId, $bmIds);
if (empty($storeRows)) {
$storeRows = $this->getO2oStoreRows($bmIds, $arrOrder);
}
break;
case '2': #先门店仓再电商仓
$storeRows = $this->getO2oStoreRows($bmIds, $arrOrder);
if (empty($storeRows)) {
$storeRows = $this->getBranchStoreRows($arrBranchId, $bmIds);
}
break;
case '3': #同时门店仓电商仓,优先门店仓
$branchStoreRows = $this->getBranchStoreRows($arrBranchId, $bmIds);
$o2oStoreRows = $this->getO2oStoreRows($bmIds, $arrOrder);
$storeRows = $o2oStoreRows;
foreach ($branchStoreRows as $v) {
$storeRows[] = $v;
}
break;
case '4': #同时电商仓门店仓,优先电商仓
$branchStoreRows = $this->getBranchStoreRows($arrBranchId, $bmIds);
$o2oStoreRows = $this->getO2oStoreRows($bmIds, $arrOrder);
$storeRows = $branchStoreRows;
foreach ($o2oStoreRows as $v) {
$storeRows[] = $v;
}
break;
case '5': #仅门店仓
$storeRows = $this->getO2oStoreRows($bmIds, $arrOrder);
break;
default: #仅电商仓
$storeRows = $this->getBranchStoreRows($arrBranchId, $bmIds);
break;
}
return $storeRows;
}
protected function getBranchStoreRows($arrBranchId, $bmIds)
{
/* $modelBp = app::get('ome')->model('branch_product');
$bpFilter = array(
'product_id' => $bmIds,
'branch_id' => $arrBranchId,
'filter_sql' => 'store>store_freeze',
);
$storeRows = $modelBp->getList('branch_id, product_id, store, store_freeze', $bpFilter); */
$arrBranchId = $this->filterBranchesByStoreControl($arrBranchId);
$storeRows = $this->batchStoreFromRedis($bmIds, $arrBranchId);
return $this->sortBranchById($storeRows, $arrBranchId);
}
protected function batchStoreFromRedis($bmIds, $arrBranchId)
{
$storeRows = [];
foreach ($bmIds as $product_id) {
foreach ($arrBranchId as $branch_id) {
$rs = ome_branch_product::storeFromRedis(['branch_id' => $branch_id, 'product_id' => $product_id]);
if(!$rs[0]) {
continue;
}
if($rs[2]['store'] <= $rs[2]['store_freeze']) {
continue;
}
$storeRows[] = [
'branch_id' => $branch_id,
'product_id' => $product_id,
'store' => $rs[2]['store'],
'store_freeze' => $rs[2]['store_freeze'],
];
}
}
return $storeRows;
}
#storeRows 根据 arrBranchId 排序
protected function sortBranchById($storeRows, $arrBranchId)
{
if (empty($storeRows)) {
return array();
}
$tmpStore = array();
foreach ($storeRows as $v) {
$index = array_search($v['branch_id'], $arrBranchId);
if ($index !== false) {
$tmpStore[$index][] = $v;
}
}
ksort($tmpStore);
$ttmpStore = array();
foreach ($tmpStore as $v) {
foreach ($v as $vv) {
$ttmpStore[] = $vv;
}
}
return $ttmpStore;
}
protected function getO2oStoreRows($bmIds, $arrOrder)
{
//如果全局不管控供货关系即门店不管控库存
$supply_relation = app::get('o2o')->getConf('o2o.ctrl.supply.relation');
if ($supply_relation != 'true') {
//return array();
}
#启用的门店
$branchLib = kernel::single('ome_interface_branch');
$branchRows = $branchLib->getList('branch_id,branch_bn', array('b_type' => 2, 'b_status' => 1, 'is_ctrl_store' => '1'), 0, -1);
if (empty($branchRows)) {
return array();
}
$refuse_stores = app::get('o2o')->model('store_refuse_analysis')->get_refuse_stores(array_column($arrOrder, 'order_id'));
$branchIds = array();
foreach ($branchRows as $v) {
// 排除掉拒绝的门店
if (in_array($v['branch_bn'], $refuse_stores)) {
continue;
}
$branchIds[] = $v['branch_id'];
}
#商品库存
$productStoreObj = app::get('ome')->model('branch_product');
$psFilter = array(
'product_id' => $bmIds,
'branch_id' => $branchIds,
'filter_sql' => 'store>store_freeze',
);
// $psRows = $productStoreObj->getList('branch_id,product_id,store,store_freeze', $psFilter);
$storeRows = $this->batchStoreFromRedis($bmIds, $branchIds);
if (empty($storeRows)) {
return array();
}
#门店权重排序
$o2oStoreId = app::get('o2o')->model('store')->getList('branch_id', array('branch_id' => $psFilter['branch_id']), 0, -1, 'priority desc');
$arrO2oBranchId = array_map('current', $o2oStoreId);
return $this->sortBranchById($storeRows, $arrO2oBranchId);
}
protected function writeSuccessLog($logResult, $arrOrder)
{
$bmIdBn = array();
$arrOrderBn = array();
foreach ($arrOrder as $order) {
foreach ($order['objects'] as $ok => $object) {
foreach ($object['items'] as $ik => $item) {
$bmIdBn[$item['product_id']] = $item['bn'];
}
}
$arrOrderBn[] = $order['order_bn'];
}
$branchId = array();
foreach ($logResult['初始值'] as $v) {
$branchId[] = $v['branch_id'];
}
$branchLib = kernel::single('ome_interface_branch');
$branchRows = $branchLib->getList('branch_id, branch_bn', array('branch_id' => $branchId));
$branchIdBn = array();
foreach ($branchRows as $v) {
$branchIdBn[$v['branch_id']] = $v['branch_bn'];
}
$logResult['id对应编码'] = [
'基础物料'=>$bmIdBn,
'仓库'=>$branchIdBn,
];
$apilogModel = app::get('ome')->model('api_log');
$log_id = $apilogModel->gen_id();
$logsdf = array(
'log_id' => $log_id,
'task_name' => '按库存就全拆结果',
'status' => 'success',
'worker' => '',
'params' => json_encode(array('store.split', $logResult), JSON_UNESCAPED_UNICODE), #longtext
'msg' => '', #text json字符串
'log_type' => '',
'api_type' => 'response',
'memo' => '',
'original_bn' => $arrOrderBn[0],
'createtime' => time(),
'last_modified' => time(),
'msg_id' => '',
);
$apilogModel->insert($logsdf);
}
/**
* 过滤出管控库存的仓库
* @param array $branchIds 仓库ID数组
* @return array 管控库存的仓库ID数组
*/
protected function filterBranchesByStoreControl($branchIds)
{
if (empty($branchIds)) {
return [];
}
$branchLib = kernel::single('ome_interface_branch');
$branchList = $branchLib->getList('branch_id', ['branch_id' => $branchIds, 'is_ctrl_store' => '1'], 0, -1);
if (empty($branchList)) {
return [];
}
// 获取满足条件的分支ID集合
$branchList = array_column($branchList, null,'branch_id');
// 循环遍历原始数组删除不满足条件的分支ID
foreach ($branchIds as $bkey => $branch_id) {
if (!isset($branchList[$branch_id])) {
unset($branchIds[$bkey]);
}
}
return $branchIds;
}
}