什么是策略模式
策略模式是一种行为型设计模式,它定义了一系列算法,封装每个算法,并使它们可以相互替换。这意味着可以根据不同情况选择不同的算法,而不需要更改使用算法的对象的代码。
在策略模式中,有三个主要角色:
- 环境类(Context):它持有一个策略对象的引用,并在需要时调用策略对象的算法。
- 策略接口(Strategy):它定义了所有具体策略类必须实现的方法,它可以是抽象类或接口。
- 具体策略类(ConcreteStrategy):它实现了策略接口,具体实现了策略算法。
策略模式的优点是增强了代码的灵活性和可扩展性,可以动态切换算法,减少了各种算法类之间的耦合。同时,策略模式也符合开闭原则,易于理解和维护。
策略模式在数据同步中的使用案例
背景
公司在全国每个省份几乎都有经销商,每个经销商从公司采购产品,公司ERP系统销售出库单审核之后,需要同步到对应的经销商ERP系统的采购入库单,避免让经销商们自己手动录入,提高效率。虽然每个经销商用的都是跟总部一样的ERP系统,但是要同步的字段以及逻辑仍然会有差异,因此需要写一套代码可以根据不同的经销商灵活地切换同步的逻辑。
案例说明
UML类图
- 首先定义策略接口(Strategy)
SyncPurchaseInStockToErpInterface
,包含getInstance
方法(用于快速实例化策略类)、doSync
方法(进行数据同步)、beautifyOrder
方法(整理单据字段与格式)、beautifyMaterial
方法(整理物料信息的字段与格式)。 - 定义具体策略类
Adapter2
、Adapter3
,对应ID为2和3的经销商,同时实现SyncPurchaseInStockToErpInterface
策略接口,补充具体逻辑,因为不同的经销商单据传输的字段和数据有差异,可以在beautifyOrder
与beautifyMaterial
方法中进行特殊处理。 - 定义环境类
SyncPurchaseInStockToErp
,包含setAdapter
方法(设置具体策略)、doSync
方法(执行同步) ,根据不同的经销商调用不同的具体策略(Adapter)。
代码示例
SyncPurchaseInStockToErpInterface
接口
<?php
namespace app\command\agency\contract;
use app\command\agency\SyncPurchaseInStockToErp;
interface SyncPurchaseInStockToErpInterface
{
/**
* 获取当前实例
* @return mixed
*/
public static function getInstance();
/**
* 执行同步
* @param SyncPurchaseInStockToErp $instance
* @param $orders
* @param $agencies
* @return mixed
*/
public function doSync(SyncPurchaseInStockToErp $instance, $orders, $agencies);
/**
* 格式化同步数据
* @param $order··
* @param $agencies
* @return mixed
*/
public function beautifyOrder($order, $agencies);
/**
* 格式化物料数据
* @param $material
* @return mixed
*/
public function beautifyMaterial($material);
}
Adapter2
类
<?php
namespace app\command\agency\sync_purchase_in_stock_to_erp;
use app\command\agency\contract\SyncPurchaseInStockToErpInterface;
use app\command\agency\SyncPurchaseInStockToErp;
use app\library\enum\agency\BaseEnum;
use app\library\exception\ServiceLogicException;
use app\models\AgencyPurchaseInStock;
use app\models\AgencyPurchaseInStockPart;
use app\models\ErpBosAssistantDataDetail;
use app\models\ErpSaleOutStock;
use think\db\Query;
/**
* 北京经销商
* Class Adapter2
* @package app\command\agency\sync_purchase_in_stock_to_erp
*/
class Adapter2 implements SyncPurchaseInStockToErpInterface
{
private static $instance = null;
// 经销商ERP组织编码
const AGENCY_ORG_NUMBER = '100';
/**
* @var SyncPurchaseInStockToErp
*/
protected $syncPurchaseInStockToErpInStance;
private function __construct()
{
}
private function __clone()
{
}
public static function getInstance()
{
if (!self::$instance){
self::$instance = new self();
}
return self::$instance;
}
/**
* 执行同步
* @param SyncPurchaseInStockToErp $instance 同步总部ERP出库单到经销商
* @param $outStockOrder
* @param $agencies
* @return mixed|void
* @throws ServiceLogicException
* @throws \app\library\exception\ErpQueryException
*/
public function doSync(SyncPurchaseInStockToErp $instance, $outStockOrder, $agencies)
{
$this->syncPurchaseInStockToErpInStance = $instance;
$erpDataId = $agencies[$outStockOrder['customer_number']]['erp_data_id'] ?? '';
if (!$erpDataId){
throw new ServiceLogicException("出库单:{$outStockOrder['bill_no']}对应的经销商未配置ERP信息");
}
// 检查是否需要再次同步到经销商ERP
if (!$instance->checkIsShouldSyncToAgencyErp($erpDataId, $outStockOrder)) return true;
// 格式化采购入库单数据
$data = $this->beautifyOrder($outStockOrder, $agencies);
// 保存采购入库单到ERP
$fid = $instance->savePurchaseInStockToErp($erpDataId, $data);
// 更新门窗工匠的销售出库单明信息
$instance->updateSaleOutStockEntry($erpDataId, $fid);
// 更新门窗工匠的销售出库单信息
ErpSaleOutStock::where('id', $outStockOrder['id'])->update(['is_async' => 1, 'is_async_hopo_erp' => 1]);
}
/**
* 格式化采购入库单数据
* @param $order
* @param $agencies
* @return array
* @throws ServiceLogicException
*/
public function beautifyOrder($order, $agencies)
{
if (empty($order)){
return [];
}
$expressCompany = '';
if ($order['express_company_id']){
$expressCompany = ErpBosAssistantDataDetail::where('entry_id', $order['express_company_id'])->value('value') ?: '';
}
$saveData = [ 'IsDeleteEntry' => false, 'EnableApmTrace' => true ];
$saveData['Model']['FSupplierId']['FNumber'] = 'VEN00001'; // 供应商
$saveData['Model']['F_ZHR_ZBCKD'] = $order['bill_no']; // 总部出库单
$saveData['Model']['F_ZHR_YSGS'] = $expressCompany; // 运输公司
$saveData['Model']['F_ZHR_YSDH'] = $order['express_no']; // 运输单号
$saveData['Model']['FDate'] = $order['create_date']; // 入库日期
$erpDataId = $agencies[$order['customer_number']]['erp_data_id'];
if (!isset($order['entry']) || empty($order['entry'])){
throw new ServiceLogicException("出库单:{$order['bill_no']}缺少单据明细信息");
}
$FEntity = [];
foreach ($order['entry'] as $item) {
$this->syncPurchaseInStockToErpInStance->checkAgencyErpMaterial($erpDataId, $item['material'], self::AGENCY_ORG_NUMBER, 2);
// 判断物料是否存在ERP或是否已审核
// 获取历史价格
$historyPrice = $diffAmount = 0;
$FGiveAway = bccomp($item['tax_price'], 0, 2) <= 0;
if (!$FGiveAway){ // 不是赠品
$historyPrice = $this->getHistoryPrice($agencies[$order['customer_number']]['agency_id'], $item['material']);
$diffAmount = $historyPrice ? bcsub($item['tax_price'], $historyPrice, 2) : 0; // 差异额
}
// 组装ERP参数
$FEntity[] = [
'FMaterialId' => [ 'FNumber' => $item['material'] ],
'FStockId' => [ 'FNumber' => BaseEnum::AGENCY_STOCK[$order['customer_number']] ],
'FStockLocId' => ['FSTOCKLOCID__FF100001' => ['FNumber' => 'A01']],
'FRemainInStockQty' => $item['real_qty'],
'FRealQty' => $item['real_qty'],
'FPriceUnitQty' => $item['real_qty'],
'FTaxPrice' => $item['tax_price'],
'F_WH_BOASSFID' => $order['fid'],
'F_WH_BOASSFENTRY' => $item['entry_id'],
'F_WH_FHFID' => $order['entity_link_bill_id'],
'F_WH_FHFENTRY' => $item['entity_link_id'],
'F_wh_LSPrice' => $historyPrice,
'F_WH_CY' => $diffAmount,
'FGiveAway' => $FGiveAway,
'FNote' => "总部" . date("Y-m-d", strtotime($order['create_date']))
];
}
$saveData['Model']['FInStockEntry'] = $FEntity;
return $saveData;
}
/**
* 格式化物料数据
* @param $materialNumber
* @return array
* @throws ServiceLogicException
*/
public function beautifyMaterial($materialNumber)
{
$boassErpMaterial = $this->syncPurchaseInStockToErpInStance->getMaterial($materialNumber);
$jsonData = [];
$jsonData['FCreateOrgId'] = '001'; //创建者 非必录
$jsonData['NeedUpDateFields'] = []; //需要保存的字段,格式["fieldkey1","fieldkey2","entitykey1",...],数组类型(非必录)
$jsonData['NeedReturnFields'] = []; //需要返回的结果字段,格式["fieldkey","entitykey.fieldkey",...](非必录)
$model['FNumber'] = $boassErpMaterial->product_sn; // 物料编码
$model['FName'] = $boassErpMaterial->product_name; // 物料名称
$model['F_SQ_BZXNSL'] = $boassErpMaterial->container_amount; // 包装箱内数量
$model['F_SQ_BZHNSL1'] = $boassErpMaterial->box_amount; // 包装盒内数量
$model['FSpecification'] = $boassErpMaterial->specification; // 规格型号
$model['SubHeadEntity'] = [
'FErpClsID' => '1', // 物料属性
// 'FBaseUnitId' => ['FNumber' => $boassErpMaterial->unit_number] , // 物料单位
'FCategoryID' => ['FNumber' => 'CHLB05_SYS'] // 存货类别
];
$model['SubHeadEntity1'] = [
'FStockId' => ['FNumber' => 'CK001']
];
$jsonData['Model'] = $model;
return $jsonData;
}
/**
* 获取历史价格
* @param $agencyId
* @param $materialNumber
* @return int|mixed
*/
protected function getHistoryPrice($agencyId, $materialNumber)
{
$inStockDate = BaseEnum::AGENCY_HISTORY_PURCHASE_IN_STOCK_DATE[$agencyId] ?? '2022-08-01';
$taxPrice = AgencyPurchaseInStockPart::alias('apisp')
->leftJoin(AgencyPurchaseInStock::getTable() . ' apis', 'apisp.purchase_in_stock_id = apis.id')
->where('apisp.agency_id', $agencyId)
->where('apisp.material_number', $materialNumber)
->where(function (Query $query) use ($inStockDate){
$query->where('apis.document_status', 'C')
->whereOr('apis.in_stock_date', $inStockDate);
})
->where('apisp.tax_price', '<>', 0)
->order('apisp.entry_id', 'desc')->value('tax_price');
return $taxPrice ?: 0;
}
}
Adapter3
类
<?php
namespace app\command\agency\sync_purchase_in_stock_to_erp;
use app\command\agency\contract\SyncPurchaseInStockToErpInterface;
use app\command\agency\SyncPurchaseInStockToErp;
use app\library\enum\agency\BaseEnum;
use app\library\exception\ServiceLogicException;
use app\models\AgencyPurchaseInStock;
use app\models\AgencyPurchaseInStockPart;
use app\models\ErpSaleOutStock;
use app\service\agency\erp\ErpQuery;
use think\db\Query;
/**
* 河南经销商
* Class Adapter1539
* @package app\command\agency\sync_purchase_in_stock_to_erp
*/
class Adapter3 implements SyncPurchaseInStockToErpInterface
{
private static $instance = null;
// 经销商ERP组织编码
const AGENCY_ORG_NUMBER = '009';
/**
* @var SyncPurchaseInStockToErp
*/
protected $syncPurchaseInStockToErpInStance;
private function __construct()
{
}
private function __clone()
{
}
public static function getInstance()
{
if (!self::$instance){
self::$instance = new self();
}
return self::$instance;
}
/**
* 执行同步
* @param SyncPurchaseInStockToErp $instance 同步总部ERP出库单到经销商
* @param $outStockOrder
* @param $agencies
* @return mixed|void
* @throws ServiceLogicException
*/
public function doSync(SyncPurchaseInStockToErp $instance, $outStockOrder, $agencies)
{
$this->syncPurchaseInStockToErpInStance = $instance;
$erpDataId = $agencies[$outStockOrder['customer_number']]['erp_data_id'] ?? '';
if (!$erpDataId){
throw new ServiceLogicException("出库单:{$outStockOrder['bill_no']}对应的经销商未配置ERP信息");
}
// 检查是否需要再次同步到经销商ERP
if (!$instance->checkIsShouldSyncToAgencyErp($erpDataId, $outStockOrder)) return true;
// 格式化采购入库单数据
$data = $this->beautifyOrder($outStockOrder, $agencies);
// 保存采购入库单到ERP
$fid = $instance->savePurchaseInStockToErp($erpDataId, $data);
// 更新门窗工匠的销售出库单明信息
$instance->updateSaleOutStockEntry($erpDataId, $fid);
// 更新门窗工匠的销售出库单信息
ErpSaleOutStock::where('id', $outStockOrder['id'])->update(['is_async' => 1, 'is_async_hopo_erp' => 1]);
}
/**
* 格式化采购入库单数据
* @param $order
* @param $agencies
* @return array
* @throws ServiceLogicException
*/
public function beautifyOrder($order, $agencies)
{
if (empty($order)){
return [];
}
$saveData = [ 'IsDeleteEntry' => false, 'EnableApmTrace' => true ];
// !!!采购组织一定要放在供应商前面,不然会报缺少供应商字段
$saveData['Model']['FStockOrgId']['FNumber'] = self::AGENCY_ORG_NUMBER; // 收料组织
$saveData['Model']['FPurchaseOrgId']['FNumber'] = self::AGENCY_ORG_NUMBER; // 采购组织
$saveData['Model']['FSupplierId']['FNumber'] = 'VEN00025'; // 供应商
$saveData['Model']['FOwnerIdHead']['FNumber'] = self::AGENCY_ORG_NUMBER; // 货主
$saveData['Model']['FDate'] = $order['create_date']; // 入库日期
if (!isset($order['entry']) || empty($order['entry'])){
throw new ServiceLogicException("出库单:{$order['bill_no']}缺少单据明细信息");
}
$erpDataId = $agencies[$order['customer_number']]['erp_data_id'];
$FEntity = [];
foreach ($order['entry'] as $item) {
$materialId = $this->syncPurchaseInStockToErpInStance->checkAgencyErpMaterial($erpDataId, $item['material'], self::AGENCY_ORG_NUMBER);
$material = ErpQuery::setDataId($erpDataId)
->form('BD_MATERIAL')
->field("FBaseUnitId.FNumber")
->where("FMATERIALID = '{$materialId}'")
->page(1, 1)
->select();
if (empty($material)) throw new ServiceLogicException('物料不存在');
$unit = $material[0][0];
// 判断物料是否存在ERP或是否已审核
// 获取历史价格
$historyPrice = $diffAmount = 0;
$FGiveAway = bccomp($item['tax_price'], 0, 2) <= 0;
if (!$FGiveAway){ // 不是赠品
$historyPrice = $this->getHistoryPrice($agencies[$order['customer_number']]['agency_id'], $item['material']);
$diffAmount = $historyPrice ? bcsub($item['tax_price'], $historyPrice, 2) : 0; // 差异额
}
// 组装ERP参数
$FEntity[] = [
'FOWNERTYPEID' => 'BD_OwnerOrg',
'FOWNERID' => ['FNumber' => self::AGENCY_ORG_NUMBER], // 货主
'FMaterialId' => [ 'FNumber' => $item['material'] ],
'FUnitID' => ['FNumber' => $unit],
'FPriceUnitID' => ['FNumber' => $unit],
'FRemainInStockUnitId' => ['FNumber' => $unit],
'FStockId' => [ 'FNumber' => BaseEnum::AGENCY_STOCK[$order['customer_number']] ],
'FStockStatusId' => ['FNumber' => 'KCZT01_SYS'],
'FRemainInStockQty' => $item['real_qty'],
'FRealQty' => $item['real_qty'],
'FPriceUnitQty' => $item['real_qty'],
'FTaxPrice' => $item['tax_price'],
'F_WH_BOASSFID' => $order['fid'],
'F_WH_BOASSFENTRY' => $item['entry_id'],
'F_WH_FHFID' => $order['entity_link_bill_id'],
'F_WH_FHFENTRY' => $item['entity_link_id'],
'F_wh_LSPrice' => $historyPrice,
'F_WH_CY' => $diffAmount,
'FGiveAway' => $FGiveAway
];
}
$saveData['Model']['FInStockEntry'] = $FEntity;
return $saveData;
}
/**
* 格式化物料数据
* @param $materialNumber
* @return array
* @throws ServiceLogicException
*/
public function beautifyMaterial($materialNumber)
{
$boassErpMaterial = $this->syncPurchaseInStockToErpInStance->getMaterial($materialNumber);
$jsonData = [];
$jsonData['NeedUpDateFields'] = []; //需要保存的字段,格式["fieldkey1","fieldkey2","entitykey1",...],数组类型(非必录)
$jsonData['NeedReturnFields'] = []; //需要返回的结果字段,格式["fieldkey","entitykey.fieldkey",...](非必录)
$model['FCreateOrgId'] = ['FNumber' => self::AGENCY_ORG_NUMBER]; //创建组织
$model['FUseOrgId'] = ['FNumber' => self::AGENCY_ORG_NUMBER]; // 使用组织
$model['FNumber'] = $boassErpMaterial->product_sn; // 物料编码
$model['FName'] = $boassErpMaterial->product_name; // 物料名称
$model['F_SQ_BZXNSL'] = $boassErpMaterial->container_amount; // 包装箱内数量
$model['F_SQ_BZHNSL1'] = $boassErpMaterial->box_amount; // 包装盒内数量
$model['FSpecification'] = $boassErpMaterial->specification; // 规格型号
$model['SubHeadEntity'] = [
'FErpClsID' => '1', // 物料属性
'FBaseUnitId' => ['FNumber' => $boassErpMaterial->unit_number] , // 物料单位
'FCategoryID' => ['FNumber' => 'CHLB05_SYS'] // 存货类别
];
$model['SubHeadEntity1'] = [
'FStockId' => ['FNumber' => 'CK001']
];
$jsonData['Model'] = $model;
return $jsonData;
}
/**
* 获取历史价格
* @param $agencyId
* @param $materialNumber
* @return int|mixed
*/
protected function getHistoryPrice($agencyId, $materialNumber)
{
$inStockDate = BaseEnum::AGENCY_HISTORY_PURCHASE_IN_STOCK_DATE[$agencyId] ?? '2022-08-01';
$taxPrice = AgencyPurchaseInStockPart::alias('apisp')
->leftJoin(AgencyPurchaseInStock::getTable() . ' apis', 'apisp.purchase_in_stock_id = apis.id')
->where('apisp.agency_id', $agencyId)
->where('apisp.material_number', $materialNumber)
->where(function (Query $query) use ($inStockDate){
$query->where('apis.document_status', 'C')
->whereOr('apis.in_stock_date', $inStockDate);
})
->where('apisp.tax_price', '<>', 0)
->order('apisp.entry_id', 'desc')->value('tax_price');
return $taxPrice ?: 0;
}
}
SyncPurchaseInStockToErp
类
<?php
namespace app\command\agency;
use app\admin\service\MaterialService;
use app\command\agency\contract\SyncPurchaseInStockToErpInterface;
use app\library\enum\agency\AgencyDataSyncEnum;
use app\library\enum\agency\BaseEnum;
use app\library\exception\ServiceLogicException;
use app\models\AgencyDatabase;
use app\models\ErpMaterial;
use app\models\ErpSaleOutStock;
use app\models\ErpSaleOutStockEntry;
use app\models\UserInfo;
use app\service\agency\erp\ErpQuery;
use app\service\notice\YunZhiJiaNoticeRobot;
use think\console\input\Option;
use think\db\Query;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
use app\common\MyLog;
use think\facade\Cache;
/**
* 同步总部销售出库单到经销商ERP的采购入库单
*/
class SyncPurchaseInStockToErp extends Command
{
/**
* @var SyncPurchaseInStockToErpInterface
*/
protected $adapter;
protected function configure()
{
$this->setName('SyncPurchaseInStockToErp')
->addOption('agency_id', null, Option::VALUE_REQUIRED, "经销商ID,多个以英文逗号隔开")
->addOption('create_date', null, Option::VALUE_REQUIRED, "创建日期")
->addArgument('id', Argument::OPTIONAL, 0)
->setDescription('同步总部ERP销售出库单信息到经销商ERP采购入库单(新版)');
}
protected function execute(Input $input, Output $output)
{
$id = trim($input->getArgument('id'));
$agencyIds = $input->getOption('agency_id') ?: '469';
$createDate = $input->getOption('create_date') ?: '';
// 获取经销商ERP信息
$agencies = $this->getAgency($agencyIds);
foreach ($agencies as $agencyErpSn => $agency) {
try {
// 获取需要同步的采购入库单
$createDate = AgencyDataSyncEnum::AGENCY_SYNC_PURCHASE_IN_STOCK_ORDER_DATE[$agency['agency_id']] ?? '';
if (!$createDate){
continue;
}
$outStockOrders = $this->getOutStockOrder(['create_date' => $createDate, 'agency_sn' => $agencyErpSn, 'id' => $id]);
$this->doSync($agencies, $outStockOrders);
}catch (\Exception $e){
MyLog::errorLog("同步总部出库单到经销商ERP采购入库单发生异常,经销商ID:{$agency['agency_id']}", $e->getMessage(), 'rsync_agency_purchase_in_stock');
}
}
$output->writeln('商品数据成功同步至经销商ERP!');
}
/**
* 执行同步
* @param $agencies array 经销商信息
* @param $outStockOrders array 出库单信息
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function doSync($agencies, $outStockOrders)
{
foreach ($outStockOrders as $outStockOrder) {
$agencyId = $agencies[$outStockOrder['customer_number']]['agency_id'] ?? 0;
try {
if (!$agencyId){
throw new ServiceLogicException("对应的经销商未配置agency_id");
}
$className = "app\\command\\agency\\sync_purchase_in_stock_to_erp\\Adapter{$agencyId}";
$adapter = $className::getInstance();
MyLog::infoLog('销售出库单信息', $outStockOrder, 'rsync_agency_purchase_in_stock');
$adapter->doSync((new self())->setAdapter($adapter), $outStockOrder, $agencies);
MyLog::infoLog('success', "同步成功:{$outStockOrder['bill_no']}", 'rsync_agency_purchase_in_stock');
}catch (\Exception $e){
$agencyName = UserInfo::where('user_id', $agencyId)->value('company_name');
$msg = "{$agencyName}出库单{$outStockOrder['bill_no']}同步到经销商ERP采购入库单失败,报错信息:" . $e->getMessage() . ' 位于文件:' . $e->getFile() . ' 第' . $e->getLine() . '行';
MyLog::errorLog('error', $msg, 'rsync_agency_purchase_in_stock');
YunZhiJiaNoticeRobot::sendSyncDataExceptionMsg($msg);
continue;
}
}
}
public function setAdapter(SyncPurchaseInStockToErpInterface $adapter)
{
$this->adapter = $adapter;
return $this;
}
/**
* 获取经销商信息
* @param $agencyIds
* @return array
* @throws ServiceLogicException
*/
protected function getAgency($agencyIds)
{
$query = AgencyDatabase::where('status', 1)
->where('erp_sn', '<>', '');
$agencyIds && $query->where('agency_id', 'in', explode(',', $agencyIds));
$agencies = $query->field('erp_sn,erp_data_id,agency_id')
->select()
->toArray();
if (empty($agencies)){
throw new ServiceLogicException('经销商ERP信息未在数据库配置');
}
return array_column($agencies, null, 'erp_sn');
}
/**
* 获取总部销售出库单信息
* @param $params
* @return array|array[]|\array[][]|\array[][][]
*/
protected function getOutStockOrder($params)
{
$createDate = $params['create_date'] ?? '';
$id = $params['id'] ?? 0;
$where = [];
$where[] = ['is_new','=',1];
$where[] = ['is_async','=',0];
$where[] = ['is_replacement_order', '=', 0];
$where[] = ['customer_number', '=', $params['agency_sn']];
$createDate && $where[] = ['create_date', '>=', $createDate];
$id && $where[] = ['id', '=', $id];
return ErpSaleOutStock::where($where)
->field('id,customer_number,fid,bill_no,entity_link_bill_id,express_no,express_company_id,approve_date,create_date,head_location_number')
->with([
'entry' => function(Query $query){
$query->order('entry_id asc');
}
])
->limit(10)
->select()
->toArray();
}
/**
* 检查是否需要同步到经销商采购入库单
* @param $erpDataId
* @param $order
* @return bool
* @throws ServiceLogicException
* @throws \app\library\exception\ErpQueryException
*/
public function checkIsShouldSyncToAgencyErp($erpDataId, $saleOutStockOrder)
{
$fhFid = Cache::store('redis')->get('fhFid');
if ($fhFid && $fhFid == $saleOutStockOrder['entity_link_bill_id']){
Cache::store('redis')->rm('fhFid');
return true;
}
$order = ErpQuery::setDataId($erpDataId)
->form('STK_InStock')
->where("F_WH_FHFID = '{$saleOutStockOrder['entity_link_bill_id']}'")
->where("F_WH_BOASSFID = '{$saleOutStockOrder['fid']}'")
->field("FID,FDocumentStatus")
->select();
if (empty($order)){
return true;
}
if ($order[0][1] != 'A'){ // 不是创建状态不用再次同步到ERP
ErpSaleOutStock::where('id', $saleOutStockOrder['id'])->update(['is_async' => 1, 'is_async_hopo_erp' => 1]);
return false;
}
// 如果已经存在创建状态的采购入库单,则把它删除
$this->deletePurchaseInStockInErp($erpDataId, $order[0][0]);
return true;
}
/**
* 检查经销商ERP物料信息是否存在并保存、提交、审核
* @param $erpDataId string ERP账套ID
* @param $materialNumber string 物料编码
* @param string $useOrgNumber 组织编码
* @return mixed
* @throws ServiceLogicException
* @throws \app\library\exception\ErpQueryException
*/
public function checkAgencyErpMaterial($erpDataId, $materialNumber, $useOrgNumber = '', $agencyId = 0)
{
$query = ErpQuery::setDataId($erpDataId)
->form('BD_MATERIAL')
->where("FNumber = '$materialNumber'")
->field("FMATERIALID,FNumber,FName,FDocumentStatus,FForbidStatus,FCreateOrgId,FCreateOrgId.FNumber,FUseOrgId,FUseOrgId.FNumber");
$material = $query->order("FMATERIALID desc")
->select();
if (empty($material)){ // 如果当前账套所有组织都不存在此物料,则使用当前组织创建
$id = $this->saveMaterialToErp($erpDataId, $materialNumber);
$this->submitMaterialInErp($erpDataId, $id);
$this->auditMaterialInErp($erpDataId, $id);
return $id;
}
$createOrgMaterialId = $this->getCreateOrgMaterialId($material); // 创建组织的物料ID
$useOrgNumberKey = 8;
$materialMap = array_column($material, null, $useOrgNumberKey);
if (!isset($materialMap[$useOrgNumber])){ // 如果当前组织没有被分配,则进行分配(因为同一个账套不同组织不能拥有相同编码,只能通过分配的方式解决)
$id = $this->allocateMaterialInErp($erpDataId, $createOrgMaterialId, BaseEnum::AGENCY_ERP_ORG_FID_MAP[$agencyId][$useOrgNumber]);
$useOrgMaterial = ErpQuery::setDataId($erpDataId)
->form('BD_MATERIAL')
->field("FMaterialId")
->where("FUseOrgId.FNumber = '$useOrgNumber'")
->where("FNumber = '$materialNumber'")
->page(1, 1)
->select();
if (empty($useOrgMaterial)) throw new ServiceLogicException("分配物料:$materialNumber 后在ERP查找不到");
$this->submitMaterialInErp($erpDataId, $useOrgMaterial[0][0]);
$this->auditMaterialInErp($erpDataId, $useOrgMaterial[0][0]);
return $id;
}
$material = $materialMap[$useOrgNumber];
$documentStatus = $material[3];
$forbidStatus = $material[4];
$id = $material[0];
if ($forbidStatus == 'B'){ // 如果被禁用则进行反禁用
$this->enableMaterialInErp($erpDataId, $material[0]);
}
// 如果物料的状态为创建或重新审核或暂存,则提交、审核
if ($documentStatus == 'A' || $documentStatus == 'D' || $documentStatus == 'Z'){
$this->submitMaterialInErp($erpDataId, $id);
$this->auditMaterialInErp($erpDataId, $id);
}
// 如果在审核中,直接提交审核
if ($documentStatus == 'B'){
$this->auditMaterialInErp($erpDataId, $id);
}
return $id;
}
protected function getCreateOrgMaterialId($materials)
{
foreach ($materials as $material) {
if ($material[5] == $material[7]){
return $material[0];
}
}
return $materials[0][0];
}
/**
* 获取门窗工匠的物料信息
* @param $materialNumber
* @return ErpMaterial
* @throws ServiceLogicException
*/
public function getMaterial($materialNumber)
{
$boassErpMaterial = ErpMaterial::where('product_sn', $materialNumber)
->field('product_sn,product_name,container_amount,box_amount,specification,unit_number')
->find();
if (!$boassErpMaterial){
MaterialService::getInstance()->syncMaterialByFNumber($materialNumber, 'system');
$boassErpMaterial = ErpMaterial::where('product_sn', $materialNumber)
->field('product_sn,product_name,container_amount,box_amount,specification,unit_number')
->find();
}
if (!$boassErpMaterial){
throw new ServiceLogicException("创建物料失败:{$materialNumber},没有在门窗工匠中找到该物料信息");
}
return $boassErpMaterial;
}
/**
* 保存物料到经销商ERP
* @param $data
* @return mixed
* @throws ServiceLogicException
*/
public function saveMaterialToErp($erpDataId, $materialNumber)
{
$jsonData = $this->adapter->beautifyMaterial($materialNumber);
$result = ErpQuery::setDataId($erpDataId)
->form('BD_MATERIAL')
->save($jsonData);
$isSuccess = $result['content']['Result']['ResponseStatus']['IsSuccess'] ?? false;
if (!$isSuccess){
$error = $result['content']['Result']['ResponseStatus']['Errors'][0]['Message'] ?? '未知错误';
throw new ServiceLogicException( "保存物料失败:" . $error);
}
return $result['content']['Result']['Id'];
}
/**
* 提交物料信息
* @param $erpDataId
* @param $ids
* @throws ServiceLogicException
*/
public function submitMaterialInErp($erpDataId, $ids)
{
$result = ErpQuery::setDataId($erpDataId)
->form('BD_MATERIAL')
->submit(['Ids' => $ids]);
$isSuccess = $result['content']['Result']['ResponseStatus']['IsSuccess'] ?? false;
if (!$isSuccess){
$error = $result['content']['Result']['ResponseStatus']['Errors'][0]['Message'] ?? '未知错误';
throw new ServiceLogicException("提交物料失败:" . $error);
}
}
/**
* 审核物料信息
* @param $erpDataId
* @param $ids
* @throws ServiceLogicException
*/
public function auditMaterialInErp($erpDataId, $ids)
{
$result = ErpQuery::setDataId($erpDataId)
->form('BD_MATERIAL')
->audit(['Ids' => $ids]);
$isSuccess = $result['content']['Result']['ResponseStatus']['IsSuccess'] ?? false;
if (!$isSuccess){
$error = $result['content']['Result']['ResponseStatus']['Errors'][0]['Message'] ?? '未知错误';
throw new ServiceLogicException( "审核物料失败:" . $error);
}
}
/**
* 分配物料
* @param $erpDataId
* @param $ids string 物料ID,多个使用英文逗号隔开
* @param $orgIds string 组织ID字符串,多个使用英文逗号隔开
* @throws ServiceLogicException
*/
public function allocateMaterialInErp($erpDataId, $ids, $orgIds)
{
$result = ErpQuery::setDataId($erpDataId)
->form('BD_MATERIAL')
->allocate(['PkIds' => $ids, 'TOrgIds' => $orgIds]);
$isSuccess = $result['content']['Result']['ResponseStatus']['IsSuccess'] ?? false;
if (!$isSuccess){
$error = $result['content']['Result']['ResponseStatus']['Errors'][0]['Message'] ?? '未知错误';
throw new ServiceLogicException( "分配物料失败:" . $error);
}
return $result['content']['Result']['ResponseStatus']['SuccessEntitys'][0]['Id'];
}
/**
* 反禁用物料
* @param $erpDataId
* @param $ids
* @throws ServiceLogicException
*/
public function enableMaterialInErp($erpDataId, $ids)
{
$result = ErpQuery::setDataId($erpDataId)
->form('BD_MATERIAL')
->enable(['Ids' => $ids]);
$isSuccess = $result['content']['Result']['ResponseStatus']['IsSuccess'] ?? false;
if (!$isSuccess){
$error = $result['content']['Result']['ResponseStatus']['Errors'][0]['Message'] ?? '未知错误';
throw new ServiceLogicException( "反禁用物料失败:" . $error);
}
}
/**
* 删除经销商采购入库单
* @param $erpDataId
* @param $ids
* @throws ServiceLogicException
*/
public function deletePurchaseInStockInErp($erpDataId, $ids)
{
$result = ErpQuery::setDataId($erpDataId)
->form('STK_InStock')
->delete(['Ids' => $ids]);
$isSuccess = $result['content']['Result']['ResponseStatus']['IsSuccess'] ?? false;
if (!$isSuccess){
$error = $result['content']['Result']['ResponseStatus']['Errors'][0]['Message'] ?? '未知错误';
throw new ServiceLogicException( "删除失败:" . $error);
}
}
/**
* 保存采购入库单
* @param $erpDataId
* @param $data
* @return mixed
* @throws ServiceLogicException
*/
public function savePurchaseInStockToErp($erpDataId, $data)
{
$result = ErpQuery::setDataId($erpDataId)
->form('STK_InStock')
->save($data);
$isSuccess = $result['content']['Result']['ResponseStatus']['IsSuccess'] ?? false;
if (!$isSuccess){
$error = $result['content']['Result']['ResponseStatus']['Errors'] ?? '[]';
throw new ServiceLogicException( "保存失败:" . json_encode($error, JSON_UNESCAPED_UNICODE) . "dataId:{$erpDataId}");
}
return $result['content']['Result']['Id'];
}
/**
* 更新门窗工匠出库单明细
* @param $erpDataId
* @param $fid
* @throws ServiceLogicException
* @throws \app\library\exception\ErpQueryException
*/
public function updateSaleOutStockEntry($erpDataId, $fid)
{
$saleOutStockEntry = ErpQuery::setDataId($erpDataId)
->form('STK_InStock')
->where("FID = '$fid'")
->field("FID,FBillNo,FInStockEntry_FEntryID,FMaterialId.FNumber,F_WH_BOASSFID,F_WH_BOASSFENTRY,FRemainInStockQty")
->select();
if (empty($saleOutStockEntry)){
throw new ServiceLogicException('获取经销商采购入库单明细信息失败');
}
foreach ($saleOutStockEntry as $item) {
$entry = ErpSaleOutStockEntry::where('entry_id', $item[5])
->where('material', $item[3])
->field('id,material,real_qty,agency_purchase_in_stock_num,agency_purchase_in_stock_fid,agency_purchase_in_stock_entry_id')
->find();
if (!$entry){
throw new ServiceLogicException("未找到总部出库单明细信息entry_id:{$item[5]}");
}
$entry->agency_purchase_in_stock_status = 1;
$inStockNum = $item[6];
if ($inStockNum >= $entry->real_qty) {
$entry->agency_purchase_in_stock_status = 2;
}
$entry->agency_purchase_in_stock_num = $inStockNum;
$entry->agency_purchase_in_stock_no = $item[1];
$entry->agency_purchase_in_stock_fid = $item[0];
$entry->agency_purchase_in_stock_entry_id = $item[2];
$entry->save();
}
}
}