1. 【新增】售后单售后原因类型支持搜索

2. 【新增】手工创建订单折扣可输入正数

3. 【优化】盘点申请单确认

4. 【修复】采购退货单模拟出库失败问题

5. 【新增】订单金额客户实付与结算金额

6. 【优化】仓库发货统计报表物料名称显示

7. 【优化】自有仓储虚拟发货逻辑

8. 【修复】基础物料分类管理问题
This commit is contained in:
chenping
2026-04-01 11:59:17 +08:00
parent 9341122827
commit 61783b7d01
754 changed files with 46179 additions and 5700 deletions

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright 2012-2026 ShopeX (https://www.shopex.cn)
*
@@ -133,4 +134,4 @@ abstract class erpapi_shop_request_abstract
return $response;
}
}
}

View File

@@ -170,6 +170,7 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
'logi_no' => $sdf['logi_no'],
'obj_bn' => $sdf['orderinfo']['order_bn'],
'company_code'=>$sdf['logi_type'],
'delivery_bn' => $sdf['delivery_bn'],
),
);
@@ -215,7 +216,7 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
}
//判断是否使用同步请求
$result = $this->__caller->call($api_method, $package_param, [], $title,10,$sdf['orderinfo']['order_bn']);
$result = $this->__caller->call($api_method, $package_param, [], $title,10,$package_param['tid']);
$result = $this->confirm_callback($result,$callback['params']);
}
@@ -306,7 +307,7 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
//订单信息
$orderModel = app::get('ome')->model('orders');
$orders = $orderModel->getList('order_id,sync,shop_type',array('order_id'=>$order_id),0,1);
$orders = $orderModel->getList('order_id,order_bn,sync,shop_type,shop_id',array('order_id'=>$order_id),0,1);
// 支持合单的回写
$orderBnArr = [];
@@ -314,7 +315,7 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
$orderBnArr = json_decode($callback_params['merged_order_sns'], 1);
if ($orderBnArr && is_array($orderBnArr)) {
// 去除掉已作废的订单
$orders = $orderModel->getList('order_id,sync,shop_type',array('order_bn|in'=>$orderBnArr, 'status|noequal'=>'dead'));
$orders = $orderModel->getList('order_id,order_bn,sync,shop_type,shop_id',array('order_bn|in'=>$orderBnArr, 'status|noequal'=>'dead'));
}
}
@@ -452,7 +453,51 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
$logFileter = array('log_id'=>$shipment_log_id, 'status' => array('send','succ'));
// 合单的回写,merge_order一起更新状态更新成succ以后不再更新
$shipmentModel->update($updateShipmentData,$logFileter);
if($status != 'succ'){
// 获取预警需要的变量
$delivery_bn = isset($callback_params['delivery_bn']) ? $callback_params['delivery_bn'] : '';
$shop_name = '';
$platform = '';
// 获取店铺信息和平台类型
if (!empty($orders)) {
$firstOrder = $orders[0];
// 获取店铺信息和平台类型
if (!empty($firstOrder['shop_id'])) {
$shopModel = app::get('ome')->model('shop');
$shopInfo = $shopModel->dump(array('shop_id' => $firstOrder['shop_id']), 'name,node_type');
if ($shopInfo) {
$shop_name = $shopInfo['name'] ? $shopInfo['name'] : '';
// 优先使用店铺的 node_type如果没有则使用订单的 shop_type
$shop_type_code = $shopInfo['node_type'] ? $shopInfo['node_type'] : ($firstOrder['shop_type'] ? $firstOrder['shop_type'] : '');
} else {
// 如果店铺信息获取失败,使用订单的 shop_type
$shop_type_code = $firstOrder['shop_type'] ? $firstOrder['shop_type'] : '';
}
} else {
// 如果没有 shop_id使用订单的 shop_type
$shop_type_code = $firstOrder['shop_type'] ? $firstOrder['shop_type'] : '';
}
// 将 shop_type 转换为中文
if ($shop_type_code) {
$platform = ome_shop_type::shop_name($shop_type_code);
if (empty($platform)) {
$platform = $shop_type_code; // 如果转换失败,使用原始值
}
}
}
kernel::single('monitor_event_notify')->addNotify('order_delivery_platform_sync_error', [
'logi_no' => $logi_no,
'order_bn' => implode(',', array_column($orders, 'order_bn')),
'delivery_bn' => $delivery_bn,
'platform' => $platform,
'shop_name' => $shop_name,
'errmsg' => $message,
]);
}
// 合单的回写,merged_order_sns一起更新状态更新成succ以后不再更新
if ($orderBnArr && is_array($orderBnArr) && $status == 'succ' && $callback_params['tid']) {
@@ -1112,20 +1157,19 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
$is_part_consign = false;
if($val['sendnum'] < $val['nums']){
$is_part_consign = true;
} else {
if(app::get('ome')->model('order_items')->db_dump(['obj_id'=>$val['order_obj_id'], 'delete'=>'false', 'filter_sql'=>'nums <> sendnum'], 'item_id')){
$is_part_consign = true;
}
}
$consignStatus[$oid] = array(
'sub_tid' => $oid, //子订单id
'is_part_consign' => $is_part_consign, //子订单是否部分发货,true:部分发货,false:全部发货;
);
//@todo天猫、淘宝子订单oid只会回写一次(组织发货单数据时,sdb_ome_shipment_log表有oid回写则会被过滤掉)
if(in_array($shop_type, array('tmall', 'taobao'))){
//去除数量字段
unset($oidList[$oid]['num']);
//子订单默认就是全部发货
$consignStatus[$oid]['is_part_consign'] = false;
}
}
@@ -1252,9 +1296,10 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
//已经打包的数量
$sdf['delivery_items'][$itemKey]['pack_nums'] += $package_num;
//data
//data(含 order_obj_id 供 is_part_consign 场景2 使用)
$packageList[$logi_no][$oid] = array(
'oid' => $oid,
'order_obj_id' => isset($itemVal['order_obj_id']) ? $itemVal['order_obj_id'] : null,
'item_type' => $itemVal['item_type'],
'goods_bn' => $itemVal['bn'],
'product_bn' => $product_bn,
@@ -1304,10 +1349,17 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
'num' => $package_num, //发货数量
);
//子订单发货情况
//子订单发货情况is_part_consign=true 仅在这两种部分发货场景出现,见下方注释)
$is_part_consign = false;
if($oidList[$oid]['sendnum'] < $oidList[$oid]['nums']){
// 场景1该子订单当前已发数量 < 订购数量(本次或历史为部分发货)
$is_part_consign = true;
} else {
// 场景2同一子订单(obj_id)下存在其他 order_item 未发完(nums<>sendnum)
$obj_id = isset($oidVal['order_obj_id']) ? $oidVal['order_obj_id'] : null;
if ($obj_id && app::get('ome')->model('order_items')->db_dump(['obj_id'=>$obj_id, 'filter_sql'=>'nums <> sendnum'], 'item_id')){
$is_part_consign = true;
}
}
$consignStatus[$oid] = array(
@@ -1315,13 +1367,9 @@ class erpapi_shop_request_delivery extends erpapi_shop_request_abstract
'is_part_consign' => $is_part_consign, //子订单是否部分发货,true:部分发货,false:全部发货;
);
//@todo天猫、淘宝子订单oid只会回写一次(组织发货单数据时,sdb_ome_shipment_log表有oid回写则会被过滤掉)
if(in_array($shop_type, array('tmall', 'taobao'))){
//去除数量字段
unset($packages[$logi_no]['goods'][$oid]['num']);
//子订单默认就是全部发货
$consignStatus[$oid]['is_part_consign'] = false;
}
}
}

View File

@@ -601,7 +601,15 @@ class erpapi_shop_request_logistics extends erpapi_shop_request_abstract
return $result;
}
/**
* 官方提货(提货物流)
* 子类可重写实现具体平台逻辑
* @param array $sdf 参数
* @return mixed
*/
public function officialPickup($sdf = [])
{
return null;
}
}

View File

@@ -88,11 +88,34 @@ class erpapi_shop_request_order extends erpapi_shop_request_abstract
return $this->doGetOrderStatusRet($rsp);
}
#获取订单发票汇总信息
public function getActuallyPay($order_bn)
{
$params = array('tid' => $order_bn);
$title = "店铺(" . $this->__channelObj->channel['name'] . ")获取前端店铺" . $order_bn . "的客户实付信息";
$rsp = $this->__caller->call(SHOP_GET_TRADE_INVOICE_SUMMARY, $params, array(), $title, 10, $order_bn);
return $this->doGetActuallyPayRet($rsp);
}
protected function doGetOrderStatusRet($rsp) {
$rsp['data'] = json_decode($rsp['data'], 1);
return $rsp;
}
protected function doGetActuallyPayRet($rsp) {
if(empty($rsp['data'])) {
$rsp['data'] = array();
} else {
$rsp['data'] = json_decode($rsp['data'], 1);
// consumer_invoice_amount
if(isset($rsp['data']['consumer_invoice_amount'])){
$rsp['data']['actually_pay'] = $rsp['data']['consumer_invoice_amount'] / 100;
}
}
return $rsp;
}
#订单编辑
public function updateIframe($order,$is_request=true,$ext=array()) {
// 默认本地编辑
@@ -400,4 +423,22 @@ class erpapi_shop_request_order extends erpapi_shop_request_abstract
* 子类可以重写此方法实现具体的订单确认逻辑
*/
public function confirm($order){}
/**
* 隐私号G组更新报备外呼主叫号码组
*
* @param array $params 单个订单信息
* @return array
*/
public function bindSecretMobiles($params)
{}
/**
* 隐私号G组查询查询报备外呼主叫号码组
*
* @param array $params 单个订单信息
* @return array
*/
public function querySecretMobiles($params)
{}
}

View File

@@ -312,10 +312,14 @@ class erpapi_shop_request_product extends erpapi_shop_request_abstract
);
$param = array_merge((array)$param,(array)$filter);
$title = "获取店铺(" . $this->__channelObj->channel['name'] .')商品';
$result = $this->__caller->call(SHOP_GET_ITEMS_ALL_RPC,$param,array(),$title,$timeout);
$shop_bn = $this->__channelObj->channel['shop_bn'];
$primary_bn = $shop_bn.'_'.date('Ymd', time());
$result = $this->__caller->call(SHOP_GET_ITEMS_ALL_RPC,$param,array(),$title,$timeout, $primary_bn);
if ($result['res_ltype'] > 0) {
for ($i=0;$i<3;$i++) {
$result = $this->__caller->call(SHOP_GET_ITEMS_ALL_RPC,$param,array(),$title,$timeout);
$primary_bn = $shop_bn.'-'.date('Ymd', time());
$result = $this->__caller->call(SHOP_GET_ITEMS_ALL_RPC,$param,array(),$title,$timeout, $primary_bn);
if ($result['res_ltype'] == 0) {
break;
}

View File

@@ -0,0 +1,369 @@
<?php
/**
* 补寄申请请求相关接口
*/
class erpapi_shop_request_reshipping extends erpapi_shop_request_abstract
{
/**
* 查询补寄详情
* @param array $params 参数dispute_id
* @return array
*/
public function get($params)
{
$title = '店铺('.$this->__channelObj->channel['name'].')查询补寄详情,(补寄单号:'.$params['dispute_id'].')';
$apiParams = array(
'dispute_id' => $params['dispute_id'],
//'fields' => 'dispute_id,status,modified,created,biz_order_id,time_out,attributes',
);
$result = $this->__caller->call(SHOP_RESHIPPING_GET, $apiParams, array(), $title, 10, $params['dispute_id']);
$rs = array();
if(isset($result['msg']) && $result['msg']){
$rs['msg'] = $result['msg'];
}elseif(isset($result['err_msg']) && $result['err_msg']){
$rs['msg'] = $result['err_msg'];
}
$rs['rsp'] = $result['rsp'];
$rs['data'] = $result['data'] ? (is_string($result['data']) ? json_decode($result['data'], true) : $result['data']) : array();
return $rs;
}
/**
* 查询补寄留言列表
* @param array $params 参数dispute_id, operator_roles, page_size, page_no
* @return array
*/
public function messages_get($params)
{
$title = '店铺('.$this->__channelObj->channel['name'].')查询补寄留言列表,(补寄单号:'.$params['dispute_id'].')';
$apiParams = array(
'dispute_id' => $params['dispute_id'],
'page_size' => isset($params['page_size']) ? $params['page_size'] : 20,
'page_no' => isset($params['page_no']) ? $params['page_no'] : 1,
//'fields' => 'message_id,content,operator_roles,created,message_pics',
);
$apiParams['operator_roles'] = '1,2,3,4,5,6';
$result = $this->__caller->call(SHOP_RESHIPPING_MESSAGES_GET, $apiParams, array(), $title, 10, $params['dispute_id']);
$rs = array();
if(isset($result['msg']) && $result['msg']){
$rs['msg'] = $result['msg'];
}elseif(isset($result['err_msg']) && $result['err_msg']){
$rs['msg'] = $result['err_msg'];
}
$rs['rsp'] = $result['rsp'];
// 格式化返回数据
$data = $result['data'] ? (is_string($result['data']) ? json_decode($result['data'], true) : $result['data']) : array();
$rs['data'] = $this->_formatMessagesData($data);
return $rs;
}
/**
* 格式化留言列表数据
* @param mixed $data 原始数据
* @return array 格式化后的消息列表
*/
private function _formatMessagesData($data)
{
if (!is_array($data)) {
return array();
}
// 检查新格式:$data['result']['results']['refund_message']
if (isset($data['result']['results']['refund_message']) && is_array($data['result']['results']['refund_message'])) {
$messages = $data['result']['results']['refund_message'];
$formattedMessages = array();
foreach ($messages as $msg) {
if (!is_array($msg)) {
continue;
}
$formattedMsg = $this->_formatSingleMessage($msg);
if ($formattedMsg) {
$formattedMessages[] = $formattedMsg;
}
}
return $formattedMessages;
}
// 如果数据格式不正确,返回空数组
return array();
}
/**
* 格式化单条留言数据
* @param array $msg 原始消息数据
* @return array|null 格式化后的消息数据
*/
private function _formatSingleMessage($msg)
{
if (!is_array($msg)) {
return null;
}
$formattedMsg = array(
'message_id' => isset($msg['message_id']) ? $msg['message_id'] : (isset($msg['id']) ? $msg['id'] : ''),
'content' => isset($msg['content']) ? $msg['content'] : (isset($msg['message']) ? $msg['message'] : ''),
'operator_roles' => isset($msg['operator_roles']) ? $msg['operator_roles'] : (isset($msg['owner_role']) ? $msg['owner_role'] : (isset($msg['role']) ? $msg['role'] : '')),
'created' => isset($msg['created']) ? $msg['created'] : (isset($msg['create_time']) ? $msg['create_time'] : (isset($msg['created_time']) ? $msg['created_time'] : '')),
'message_pics' => $this->_formatMessagePics($msg),
);
// 保留其他可能有用的字段
if (isset($msg['refund_id'])) {
$formattedMsg['refund_id'] = $msg['refund_id'];
}
if (isset($msg['owner_id'])) {
$formattedMsg['owner_id'] = $msg['owner_id'];
}
if (isset($msg['owner_nick'])) {
$formattedMsg['owner_nick'] = $msg['owner_nick'];
}
if (isset($msg['message_type'])) {
$formattedMsg['message_type'] = $msg['message_type'];
}
if (isset($msg['open_uid'])) {
$formattedMsg['open_uid'] = $msg['open_uid'];
}
return $formattedMsg;
}
/**
* 格式化留言图片数组
* @param array $msg 消息数据
* @return array 图片数组
*/
private function _formatMessagePics($msg)
{
// 处理图片数组 - 支持多种字段名称和格式
if (isset($msg['message_pics']) && is_array($msg['message_pics'])) {
return $msg['message_pics'];
} elseif (isset($msg['pic_urls']) && !empty($msg['pic_urls'])) {
// pic_urls 可能是字符串(逗号分隔)或数组
if (is_array($msg['pic_urls'])) {
return $msg['pic_urls'];
} elseif (is_string($msg['pic_urls'])) {
// 尝试按逗号分割
$pics = explode(',', $msg['pic_urls']);
return array_filter(array_map('trim', $pics));
}
} elseif (isset($msg['pics']) && is_array($msg['pics'])) {
return $msg['pics'];
} elseif (isset($msg['pictures']) && is_array($msg['pictures'])) {
return $msg['pictures'];
} elseif (isset($msg['pic']) && !empty($msg['pic'])) {
return is_array($msg['pic']) ? $msg['pic'] : array($msg['pic']);
}
return array();
}
/**
* 创建补寄留言
* @param array $params 参数dispute_id, content, message_pics
* @return array
*/
public function message_add($params)
{
$title = '店铺('.$this->__channelObj->channel['name'].')创建补寄留言,(补寄单号:'.$params['dispute_id'].')';
$apiParams = array(
'dispute_id' => $params['dispute_id'],
'content' => $params['content'],
//'fields' => 'message_id,content,created',
);
if (isset($params['message_pics']) && !empty($params['message_pics'])) {
$apiParams['message_pics'] = $params['message_pics']; // 单个 base64 字符串,不需要 json_encode
}
$result = $this->__caller->call(SHOP_RESHIPPING_MESSAGE_ADD, $apiParams, array(), $title, 10, $params['dispute_id']);
$rs = array();
if(isset($result['msg']) && $result['msg']){
$rs['msg'] = $result['msg'];
}elseif(isset($result['err_msg']) && $result['err_msg']){
$rs['msg'] = $result['err_msg'];
}
$rs['rsp'] = $result['rsp'];
$rs['data'] = $result['data'] ? (is_string($result['data']) ? json_decode($result['data'], true) : $result['data']) : array();
return $rs;
}
/**
* 同意补寄申请
* @param array $params 参数dispute_id
* @return array
*/
public function agree($params)
{
$title = '店铺('.$this->__channelObj->channel['name'].')同意补寄申请,(补寄单号:'.$params['dispute_id'].')';
$apiParams = array(
'dispute_id' => $params['dispute_id'],
//'fields' => 'dispute_id,status,modified,created,biz_order_id,time_out,attributes',
);
$result = $this->__caller->call(SHOP_RESHIPPING_AGREE, $apiParams, array(), $title, 10, $params['dispute_id']);
$rs = array();
if(isset($result['msg']) && $result['msg']){
$rs['msg'] = $result['msg'];
}elseif(isset($result['err_msg']) && $result['err_msg']){
$rs['msg'] = $result['err_msg'];
}
$rs['rsp'] = $result['rsp'];
$rs['data'] = $result['data'] ? (is_string($result['data']) ? json_decode($result['data'], true) : $result['data']) : array();
return $rs;
}
/**
* 拒绝补寄申请
* @param array $params 参数dispute_id, seller_refuse_reason_id, leave_message_pics, leave_message
* @return array
*/
public function refuse($params)
{
$title = '店铺('.$this->__channelObj->channel['name'].')拒绝补寄申请,(补寄单号:'.$params['dispute_id'].')';
$apiParams = array(
'dispute_id' => $params['dispute_id'],
'seller_refuse_reason_id' => $params['seller_refuse_reason_id'],
'leave_message' => $params['leave_message'],
//'fields' => 'dispute_id,status,modified,biz_order_id',
);
if (isset($params['leave_message_pics']) && !empty($params['leave_message_pics'])) {
$apiParams['leave_message_pics'] = $params['leave_message_pics']; // 单个 base64 字符串,不需要 json_encode
}
$result = $this->__caller->call(SHOP_RESHIPPING_REFUSE, $apiParams, array(), $title, 10, $params['dispute_id']);
$rs = array();
if(isset($result['msg']) && $result['msg']){
$rs['msg'] = $result['msg'];
}elseif(isset($result['err_msg']) && $result['err_msg']){
$rs['msg'] = $result['err_msg'];
}
$rs['rsp'] = $result['rsp'];
$rs['data'] = $result['data'] ? (is_string($result['data']) ? json_decode($result['data'], true) : $result['data']) : array();
return $rs;
}
/**
* 补寄发货
* @param array $params 参数dispute_id, logistics_no, logistics_type, company_name, company_code
* @return array
*/
public function consigngoods($params)
{
$title = '店铺('.$this->__channelObj->channel['name'].')补寄发货,(补寄单号:'.$params['dispute_id'].')';
$apiParams = array(
'dispute_id' => $params['dispute_id'],
'logistics_no' => $params['logistics_no'],
'logistics_type' => isset($params['logistics_type']) ? $params['logistics_type'] : '200', // 200表示快递
//'fields' => 'dispute_id,status,modified',
);
if (isset($params['company_name'])) {
$apiParams['company_name'] = $params['company_name'];
}
if (isset($params['company_code'])) {
$apiParams['company_code'] = $params['company_code'];
}
$result = $this->__caller->call(SHOP_RESHIPPING_CONSIGNGOODS, $apiParams, array(), $title, 10, $params['dispute_id']);
$rs = array();
if(isset($result['msg']) && $result['msg']){
$rs['msg'] = $result['msg'];
}elseif(isset($result['err_msg']) && $result['err_msg']){
$rs['msg'] = $result['err_msg'];
}
$rs['rsp'] = $result['rsp'];
$rs['data'] = $result['data'] ? (is_string($result['data']) ? json_decode($result['data'], true) : $result['data']) : array();
// 记录发货日志
$log_id = uniqid($_SERVER['HOSTNAME']);
$status = ($rs['rsp']=='succ') ? 'succ' : 'fail';
$log = array(
'shopId' => $this->__channelObj->channel['shop_id'],
'ownerId' => '16777215',
'orderBn' => isset($params['order_bn']) ? $params['order_bn'] : '',
'deliveryCode' => $params['logistics_no'],
'deliveryCropCode' => isset($params['company_code']) ? $params['company_code'] : '',
'deliveryCropName' => isset($params['company_name']) ? $params['company_name'] : '',
'receiveTime' => time(),
'status' => $status,
'updateTime' => time(),
'message' => $rs['msg'] ? $rs['msg'] : '成功',
'log_id' => $log_id,
);
$shipmentLogModel = app::get('ome')->model('shipment_log');
$shipmentLogModel->insert($log);
// 更新订单同步状态
if (isset($params['order_id']) && $params['order_id']) {
$orderModel = app::get('ome')->model('orders');
$updateOrderData = array(
'sync' => $status,
'up_time' => time(),
);
$orderModel->update($updateOrderData, array('order_id' => $params['order_id'], 'sync|noequal' => 'succ'));
}
return $rs;
}
/**
* 查询拒绝原因列表
* @param array $params 参数dispute_id, dispute_type
* @return array
*/
public function refusereason_get($params)
{
$title = '店铺('.$this->__channelObj->channel['name'].')查询拒绝原因列表,(补寄单号:'.$params['dispute_id'].')';
$apiParams = array(
'dispute_id' => $params['dispute_id'],
//'fields' => 'reason_id,reason_text',
);
if (isset($params['dispute_type'])) {
$apiParams['dispute_type'] = $params['dispute_type'];
}
$result = $this->__caller->call(SHOP_RESHIPPING_REFUSEREASON_GET, $apiParams, array(), $title, 10, $params['dispute_id']);
$rs = array();
if(isset($result['msg']) && $result['msg']){
$rs['msg'] = $result['msg'];
}elseif(isset($result['err_msg']) && $result['err_msg']){
$rs['msg'] = $result['err_msg'];
}
$rs['rsp'] = $result['rsp'];
$data = $result['data'] ? (is_string($result['data']) ? json_decode($result['data'], true) : $result['data']) : array();
// 检查新格式:$data['result']['results']['reason']
if (is_array($data) && isset($data['result']['results']['reason']) && is_array($data['result']['results']['reason'])) {
$rs['data'] = $data['result']['results']['reason'];
} else {
// 如果数据格式不正确,返回空数组
$rs['data'] = array();
}
return $rs;
}
}