OrderService.php 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505
  1. <?php
  2. namespace bizGhs\order\services;
  3. use biz\ghs\classes\GhsClass;
  4. use biz\shop\classes\ShopAdminClass;
  5. use biz\shop\classes\ShopCapitalClass;
  6. use biz\shop\classes\ShopClass;
  7. use biz\shop\classes\ShopExtClass;
  8. use biz\stat\classes\StatKdClass;
  9. use biz\stat\classes\StatKhCgClass;
  10. use biz\stat\classes\StatSaleClass;
  11. use biz\wx\classes\WxMessageClass;
  12. use bizGhs\base\services\BaseService;
  13. use bizGhs\book\classes\BookItemClass;
  14. use bizGhs\custom\classes\CustomClass;
  15. use bizGhs\custom\classes\CustomDebtChangeClass;
  16. use bizGhs\custom\services\GhsRechargeSettleService;
  17. use bizGhs\clear\classes\OrderCgClearClass;
  18. use bizGhs\order\classes\OrderClass;
  19. use bizGhs\order\classes\OrderExpressClass;
  20. use bizGhs\order\classes\OrderForwardClass;
  21. use bizGhs\order\classes\OrderItemClass;
  22. use bizGhs\order\classes\OrderSendClass;
  23. use bizGhs\order\classes\RefundOrderClass;
  24. use bizGhs\order\classes\RefundOrderItemClass;
  25. use bizGhs\product\classes\ProductClass;
  26. use bizGhs\shop\classes\MainClass;
  27. use bizGhs\shop\classes\ShopMoneyChangeClass;
  28. use bizGhs\stat\classes\StatYjClass;
  29. use biz\stat\classes\StatOrderCountClass;
  30. use bizGhs\stock\classes\StockRecordClass;
  31. use bizHd\cg\classes\CgRefundClass;
  32. use bizHd\cg\services\CgRefundService;
  33. use bizHd\purchase\classes\PurchaseItemClass;
  34. use bizHd\purchase\services\PurchaseService;
  35. use bizHd\stat\classes\StatIncomeClass;
  36. use bizHd\stat\classes\StatOrderClass;
  37. use common\components\dict;
  38. use common\components\orderSn;
  39. use common\components\util;
  40. use bizHd\purchase\classes\PurchaseClass;
  41. use common\components\stringUtil;
  42. use Yii;
  43. class OrderService extends BaseService
  44. {
  45. public static $baseFile = '\bizGhs\order\classes\OrderClass';
  46. public static function arrival($order, $data)
  47. {
  48. $params = ['fhType' => 0, 'fhWl' => '', 'fhWlNo' => '',];
  49. OrderClass::orderSend($order, $params);
  50. //product [{"productId":"3674","num":0,"price":1}]
  51. $product = $data['product'] ?? '';
  52. if (empty($product)) {
  53. util::fail('请填写成本价和到货数量');
  54. }
  55. $product = json_decode($product, true);
  56. if (empty($product)) {
  57. util::fail('没有填写成本价和到货数量');
  58. }
  59. $data['product'] = $product;
  60. //供货商到货
  61. $respond = OrderClass::arrival($order, $data);
  62. $cgId = $order->purchaseId ?? 0;
  63. $cg = PurchaseClass::getById($cgId, true);
  64. if (empty($cg)) {
  65. util::fail('没有找到采购单');
  66. }
  67. //供货商花材转换为零售店花材
  68. $ids = array_column($product, 'productId');
  69. $ghsProductInfo = ProductClass::getByIds($ids);
  70. if (empty($ghsProductInfo)) {
  71. util::fail('没有花材??');
  72. }
  73. $ghsPtIdRelation = [];
  74. foreach ($ghsProductInfo as $ghsProduct) {
  75. $id = $ghsProduct['id'] ?? 0;
  76. $ptItemId = $ghsProduct['itemId'] ?? 0;
  77. $ghsPtIdRelation[$ptItemId] = $id;
  78. }
  79. $ptIds = array_keys($ghsPtIdRelation);
  80. $cgMainId = $cg->mainId ?? 0;
  81. $cgSjId = $cg->sjdId ?? 0;
  82. $cgShopId = $cg->shopId ?? 0;
  83. $cgItem = ProductClass::getAllByCondition(['mainId' => $cgMainId, 'itemId' => ['in', $ptIds]], null, '*');
  84. $ghsHdIdRelate = [];
  85. foreach ($cgItem as $it) {
  86. $ptItemId = $it['itemId'] ?? 0;
  87. $ghsItemId = $ghsPtIdRelation[$ptItemId] ?? 0;
  88. $cgItemId = $it['id'] ?? 0;
  89. $ghsHdIdRelate[$ghsItemId] = $cgItemId;
  90. }
  91. $cgProduct = [];
  92. foreach ($product as $pro) {
  93. $price = $pro['price'] ?? 0;
  94. $num = $pro['num'] ?? 0;
  95. $productId = $pro['productId'] ?? 0;
  96. $currentId = $ghsHdIdRelate[$productId] ?? 0;
  97. $cgProduct[$currentId] = ['num' => $num, 'price' => $price, 'productId' => $currentId];
  98. }
  99. //零售到货
  100. $saleRefund = $respond['refund'] ?? null;
  101. $saleRefundId = 0;
  102. if (!empty($saleRefund)) {
  103. $saleRefundId = $saleRefund->id ?? 0;
  104. }
  105. $kiloFee = $respond['kiloFee'] ?? 0;
  106. $miniKilo = $respond['miniKilo'] ?? 0;
  107. $saleRefundId = $saleRefundId;
  108. $sendCost = isset($data['sendCost']) && is_numeric($data['sendCost']) ? $data['sendCost'] : 0;
  109. $packCost = isset($data['packCost']) && is_numeric($data['packCost']) ? $data['packCost'] : 0;
  110. $cgData = [
  111. 'product' => $cgProduct,
  112. 'kiloFee' => $kiloFee,
  113. 'miniKilo' => $miniKilo,
  114. 'saleRefundId' => $saleRefundId,
  115. 'sjId' => $cgSjId,
  116. 'shopId' => $cgShopId,
  117. 'mainId' => $cgMainId,
  118. 'sendCost' => $sendCost,
  119. 'packCost' => $packCost,
  120. ];
  121. $result = PurchaseClass::arrival($cg, $cgData);
  122. $cgRefund = $result['refund'] ?? null;
  123. if (!empty($cgRefund)) {
  124. $cgRefundId = $cgRefund->id ?? 0;
  125. if (!empty($saleRefund)) {
  126. $saleRefund->cgRefundId = $cgRefundId;
  127. $saleRefund->save();
  128. }
  129. }
  130. //不是预订单,并且供货商不是源花汇、小明鲜花、淄博花超、云漫梦金鹏、海翔,则订单直接完成掉。是预订单,则确认发货时直接完成掉。有多个地方要修改,请搜索关键词zjFinish
  131. if (!empty($cg) && !empty($order)) {
  132. $ghsShopId = $order->shopId ?? 0;
  133. $ext = ShopExtClass::getByCondition(['shopId' => $ghsShopId], true, null, 'id,shopId,orderFlow');
  134. if ($ext->orderFlow == 0) {
  135. $newCg = PurchaseClass::getById($cgId, true);
  136. PurchaseClass::confirmTake($newCg);
  137. }
  138. }
  139. }
  140. //添加订单
  141. public static function createSaleOrder($data, $custom, $hasPay = 1)
  142. {
  143. //有unitType数据,说明是收银台传过来的数据,转换成和手机端结构一致
  144. $hasUnitType = array_column($data['product'], 'unitType');
  145. if (!empty($hasUnitType)) {
  146. $newProductData = [];
  147. foreach ($data['product'] as $productItem) {
  148. $unitType = $productItem['unitType'] ?? 0;
  149. $num = $productItem['num'] ?? 0;
  150. $productId = $productItem['productId'] ?? 0;
  151. $classId = $productItem['classId'] ?? 0;
  152. $itemId = $productItem['itemId'] ?? 0;
  153. $price = $productItem['price'] ?? 0;
  154. if ($unitType == 0) {
  155. $bigNum = $num;
  156. $smallNum = '';
  157. } else {
  158. $bigNum = '';
  159. $smallNum = $num;
  160. }
  161. $newProductData[] = [
  162. 'productId' => (int)$productId,
  163. 'bigNum' => (int)$bigNum,
  164. 'smallNum' => (int)$smallNum,
  165. 'classId' => (int)$classId,
  166. 'itemId' => (int)$itemId,
  167. 'price' => floatval($price),
  168. ];
  169. }
  170. $data['product'] = $newProductData;
  171. }
  172. // 开单发货时间写入 sendTime(datetime),允许补录过去日期;sendTimeWant 仍是客户要求发货日。关键词 sync_send_time
  173. $sendTimeInput = isset($data['sendTime']) ? str_replace('/', '-', trim((string)$data['sendTime'])) : '';
  174. if ($sendTimeInput === '') {
  175. $sendTimeInput = date('Y-m-d');
  176. }
  177. if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $sendTimeInput)) {
  178. $data['sendTime'] = $sendTimeInput . ' ' . date('H:i:s');
  179. } elseif (!preg_match('/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}/', $sendTimeInput)) {
  180. $data['sendTime'] = date('Y-m-d H:i:s');
  181. } else {
  182. $data['sendTime'] = $sendTimeInput;
  183. }
  184. //小票打印用到这个
  185. $sendTimeWant = date('Y-m-d', strtotime($data['sendTime']));
  186. $data['sendTimeWant'] = $sendTimeWant;
  187. //供货商销售单
  188. $returnOrder = OrderClass::addOrder($data, $custom);
  189. $saleId = $returnOrder->id ?? 0;
  190. $customSjId = $custom['sjId'] ?? 0;
  191. $customShopId = $custom['shopId'] ?? 0;
  192. $localOrder = $data['localOrder'] ?? 0;
  193. $transType = $data['transType'] ?? 0;
  194. $ghsShopAdminId = $data['shopAdminId'] ?? 0;
  195. $ghsShopAdminName = $data['shopAdminName'] ?? '';
  196. $customId = $data['customId'] ?? 0;
  197. $ghsId = $data['ghsId'] ?? 0;
  198. $ghs = GhsClass::getById($ghsId);
  199. if (empty($ghs)) {
  200. util::fail('没有找到供货商');
  201. }
  202. $ghsName = $ghs['name'] ?? '';
  203. $ghsAvatar = $ghs['avatar'] ?? '';
  204. $ghsMobile = $ghs['mobile'] ?? '';
  205. $ghsFullAddress = $ghs['fullAddress'] ?? '';
  206. $payWay = $data['payWay'] ?? dict::getDict('payWay', 'wxPay');
  207. //销售单欠款字段与采购单欠款字段转换
  208. $debt = $data['debt'] ?? 0;
  209. $purchaseDebt = $debt == 1 ? PurchaseClass::DEBT_YES : PurchaseClass::DEBT_NO;
  210. if ($hasPay == dict::getDict('hasPay', 'balance')) {
  211. $payWay = dict::getDict('payWay', 'balancePay');
  212. $purchaseDebt = PurchaseClass::DEBT_NO;
  213. } elseif ($debt == 1) {
  214. $payWay = dict::getDict('payWay', 'debtPay');
  215. }
  216. //销售单的自取还是配送字段 与 采购单的转换
  217. $sendType = $data['sendType'] ?? OrderClass::SEND_TYPE_CAR_GET;
  218. $getType = $sendType == OrderClass::SEND_TYPE_THIRD_SEND ? PurchaseClass::GET_TYPE_SEND : PurchaseClass::GET_TYPE_SELF_GET;
  219. //零售采购单时没有传price字段,供货商可以改价会传price字段,这里转成userPrice以便采购单识别使用改的价还是花材原价!
  220. $currentProduct = $data['product'];
  221. foreach ($currentProduct as $currentKey => $currentItem) {
  222. if (isset($currentItem['price'])) {
  223. $currentProduct[$currentKey]['userPrice'] = $currentItem['price'];
  224. unset($currentProduct[$currentKey]['price']);
  225. }
  226. }
  227. $remark = $data['remark'] ?? '';
  228. $xj = $data['xj'] ?? '';
  229. $treeData = $data['treeData'] ?? [];
  230. $book = $data['book'] ?? 0;
  231. //后端开单,涉及花材满减,关键词 back_order_reach_discount_price
  232. $reachDiscountPrice = $data['reachDiscountPrice'] ?? 0;
  233. $wlName = $data['wlName'] ?? '';
  234. $cgMainId = $ghs['ownMainId'] ?? 0;
  235. $purchaseData = [
  236. 'book' => $book,
  237. 'product' => $currentProduct,
  238. 'sendTimeWant' => $sendTimeWant,
  239. 'sendTime' => $data['sendTime'],
  240. 'sendCost' => $data['sendCost'] ?? '0',
  241. 'packCost' => $data['packCost'] ?? '0',
  242. 'ghsShopAdminId' => $ghsShopAdminId,
  243. 'ghsShopAdminName' => $ghsShopAdminName,
  244. 'sjId' => $customSjId,
  245. 'shopId' => $customShopId,
  246. 'saleId' => $saleId,
  247. 'ghsId' => $ghsId,
  248. 'ghsName' => $ghsName,
  249. 'ghsMobile' => $ghsMobile,
  250. 'ghsFullAddress' => $ghsFullAddress,
  251. 'ghsAvatar' => $ghsAvatar,
  252. 'customId' => $customId,
  253. 'payWay' => $payWay,
  254. 'status' => PurchaseClass::STATUS_UN_PAY,
  255. 'cgStyle' => dict::getDict('cgStyle', 'ghs'),
  256. 'debt' => $purchaseDebt,
  257. 'getType' => $getType,
  258. 'sendType' => $sendType,
  259. 'remark' => $remark,
  260. 'xj' => $xj,
  261. 'treeData' => $treeData,
  262. 'discount' => 1,
  263. 'mainId' => $cgMainId,
  264. 'wlName' => $wlName,
  265. 'localOrder' => $localOrder,
  266. 'transType' => $transType,
  267. 'reachDiscountPrice' => $reachDiscountPrice,
  268. ];
  269. if (!empty($data['deadline'])) {
  270. $purchaseData['deadline'] = $data['deadline'];
  271. }
  272. if (isset($data['modifyPrice']) && is_numeric($data['modifyPrice'])) {
  273. $purchaseData['modifyPrice'] = $data['modifyPrice'];
  274. }
  275. $purchase = PurchaseClass::addPurchase($purchaseData, $ghs);
  276. //开历史订单
  277. $historyDate = $data['historyDate'] ?? '';
  278. if (!empty($historyDate)) {
  279. $currentPayTime = $historyDate . ' 12:00:00';
  280. $returnOrder->payTime = $currentPayTime;
  281. $purchase->payTime = $currentPayTime;
  282. $purchase->save();
  283. CustomDebtChangeClass::updateByCondition(['capitalType' => 10, 'relateId' => $returnOrder->id], ['payTime' => $currentPayTime]);
  284. }
  285. $purchaseId = $purchase->id ?? 0;
  286. $returnOrder->purchaseId = $purchaseId;
  287. $returnOrder->save();
  288. //欠款、已付款、余额支付的走付款后流程
  289. if (in_array($hasPay, [dict::getDict('hasPay', 'payed'), dict::getDict('hasPay', 'debt'), dict::getDict('hasPay', 'balance')])) {
  290. // 挂账/余额:先处理销售单(含余额抵挂账),再同步采购单待结
  291. if (in_array($hasPay, [dict::getDict('hasPay', 'debt'), dict::getDict('hasPay', 'balance')])) {
  292. self::payAfter($returnOrder, $payWay);
  293. $purchase = PurchaseClass::getLockById($purchaseId);
  294. if (!empty($purchase)) {
  295. $purchase->remainDebtPrice = $returnOrder->remainDebtPrice ?? '0.00';
  296. $purchase->debtPrice = $returnOrder->debtPrice ?? '0.00';
  297. $purchase->debt = ($returnOrder->debt ?? 0) == OrderClass::DEBT_YES
  298. ? PurchaseClass::DEBT_YES : PurchaseClass::DEBT_NO;
  299. $purchase->save(false, ['remainDebtPrice', 'debtPrice', 'debt']);
  300. }
  301. PurchaseService::payAfter($purchase, $payWay);
  302. } else {
  303. PurchaseService::payAfter($purchase, $payWay);
  304. self::payAfter($returnOrder, $payWay);
  305. }
  306. //不是预订单,并且供货商不是源花汇、小明鲜花、淄博花超、云漫梦金鹏、海翔,则订单直接完成掉。是预订单,则确认发货时直接完成掉。有多个地方要修改,请搜索关键词zjFinish
  307. if (isset($returnOrder->book) && $returnOrder->book == 0) {
  308. $ghsShopId = $ghs['shopId'] ?? 0;
  309. $ext = ShopExtClass::getByCondition(['shopId' => $ghsShopId], true, null, 'id,shopId,orderFlow');
  310. if ($ext->orderFlow == 0) {
  311. $returnId = $returnOrder->id;
  312. $order = OrderClass::getById($returnId, true);
  313. if (!empty($order)) {
  314. OrderClass::confirmReach($order);
  315. }
  316. }
  317. }
  318. }
  319. return $returnOrder;
  320. }
  321. //修改订单
  322. public static function updateOrder($order, $data)
  323. {
  324. $purchaseId = $order->purchaseId ?? 0;
  325. $purchase = PurchaseClass::getLockById($purchaseId);
  326. //修改供货商销售单
  327. $returnOrder = OrderClass::updateOrder($order, $data);
  328. //供货商销售单的 0未欠款 1欠款 转成零售采购单的 1欠款 2未欠款
  329. $debt = $data['debt'] ?? 0;
  330. $purchaseDebt = $debt == 1 ? PurchaseClass::DEBT_YES : PurchaseClass::DEBT_NO;
  331. $payWay = $data['payWay'] ?? dict::getDict('payWay', 'wxPay');
  332. if ($debt == 1) {
  333. $payWay = dict::getDict('payWay', 'debtPay');
  334. }
  335. //自取还是配送订单,销售单与采购单之间转换
  336. $sendType = $data['sendType'] ?? OrderClass::SEND_TYPE_MYSELF;
  337. $getType = $sendType == OrderClass::SEND_TYPE_NO ? PurchaseClass::GET_TYPE_SELF_GET : PurchaseClass::GET_TYPE_SEND;
  338. //供货商价格是可以修改的,修改的价格一起传给采购单保存
  339. $currentProduct = $data['product'];
  340. foreach ($currentProduct as $currentKey => $currentItem) {
  341. if (isset($currentProduct[$currentKey]['price'])) {
  342. $currentProduct[$currentKey]['userPrice'] = $currentProduct[$currentKey]['price'];
  343. unset($currentProduct[$currentKey]['price']);
  344. }
  345. }
  346. $remark = $data['remark'] ?? '';
  347. $purchaseData = [
  348. 'product' => $currentProduct,
  349. 'sendTime' => date("H:i:s"),
  350. 'sendCost' => $data['sendCost'] ?: '0.00',
  351. 'payWay' => $payWay,
  352. 'status' => PurchaseClass::STATUS_UN_PAY,
  353. 'cgStyle' => dict::getDict('cgStyle', 'ghs'),
  354. 'debt' => $purchaseDebt,
  355. 'getType' => $getType,
  356. 'status' => PurchaseClass::STATUS_UN_PAY,
  357. 'remark' => $remark,
  358. ];
  359. if (isset($data['deadline']) && !empty($data['deadline'])) {
  360. $purchaseData['deadline'] = $data['deadline'];
  361. }
  362. if (isset($data['modifyPrice']) && is_numeric($data['modifyPrice'])) {
  363. //优惠之后的金额
  364. if (is_numeric($data['modifyPrice']) == false || $data['modifyPrice'] <= 0) {
  365. util::fail('金额填错了哦');
  366. }
  367. $purchaseData['modifyPrice'] = $data['modifyPrice'];
  368. }
  369. //修改采购单
  370. $purchase = PurchaseClass::updatePurchase($purchase, $purchaseData);
  371. //除了自取订单直接变成已确认,其它都进入待确认流程。订单的确认以欠款或已收款方式确认。 ssh 20210809
  372. if (isset($data['sendType']) && $data['sendType'] == OrderClass::SEND_TYPE_NO) {
  373. PurchaseService::payAfter($purchase, $payWay);
  374. self::payAfter($returnOrder, $payWay);
  375. } else {
  376. //没有进入确认流程的订单也有打印需求
  377. if ($data['needPrint'] == dict::getDict('needPrint', 'need')) {
  378. OrderClass::onlinePrint($returnOrder);
  379. $returnOrder->printNum += 1;
  380. $returnOrder->save();
  381. }
  382. }
  383. return $returnOrder;
  384. }
  385. //自取免配送流程处理 ssh 2021.1.24
  386. public static function withoutSend($order)
  387. {
  388. if (isset($order->status) == false) {
  389. util::fail('没有订单状态');
  390. }
  391. if ($order->status == OrderClass::ORDER_STATUS_COMPLETE) {
  392. util::fail('订单已完成');
  393. }
  394. //自取订单,订单直接完成。【现在有三个地方有订单完成操作】
  395. $shopId = $order->shopId ?? 0;
  396. $shop = ShopClass::getLockById($shopId);
  397. if (empty($shop)) {
  398. util::fail('没有找到门店3');
  399. }
  400. $shop->unSendOrder -= 1;
  401. $shop->sendingOrder += 1;
  402. $shop->save();
  403. //自取免配送
  404. $order->sendType = OrderClass::SEND_TYPE_NO;
  405. $order->save();
  406. //这个是采购模板的withoutSend,应另外封装方法
  407. $purchaseId = $order->purchaseId ?? 0;
  408. $purchase = PurchaseClass::getById($purchaseId, true);
  409. $pShopId = $purchase->shopId;
  410. $pShop = ShopClass::getLockById($pShopId);
  411. $pShop->cgUnSend -= 1;
  412. $pShop->cgSending += 1;
  413. $pShop->save();
  414. //订单完成
  415. self::finish($order, $shop);
  416. OrderSendClass::withoutSend($order);
  417. return OrderClass::TOTAL_FLOW;
  418. }
  419. //确认送达 ssh 2021.3.4
  420. public static function reach($order)
  421. {
  422. if ($order->status != OrderClass::ORDER_STATUS_SENDING) {
  423. util::fail('订单没有在配送状态');
  424. }
  425. //第三方配送的变更第三方订单状态
  426. if (isset($order->sendType) && $order->sendType == OrderClass::SEND_TYPE_THIRD) {
  427. $express = OrderExpressClass::getByCondition(['orderSn' => $order->orderSn], true);
  428. if (!empty($express)) {
  429. $express->status = OrderExpressClass::STATUS_COMPLETE;
  430. $express->save();
  431. }
  432. }
  433. $shopId = $order->shopId ?? 0;
  434. $shop = ShopClass::getLockById($shopId);
  435. if (empty($shop)) {
  436. util::fail('没有找到门店4');
  437. }
  438. //订单完成
  439. self::finish($order, $shop);
  440. OrderSendClass::reach($order);
  441. //是否微信通知
  442. $noNeedWxNotice = isset(Yii::$app->params['noNeedWxNotice']) ? Yii::$app->params['noNeedWxNotice'] : 0;
  443. if ($noNeedWxNotice == 0) {
  444. //微信通知
  445. WxMessageClass::reachInform($shop, $order);
  446. }
  447. }
  448. //订单完成
  449. public static function finish($order, $shop)
  450. {
  451. //销售单完成
  452. $order = OrderClass::complete($order, $shop);
  453. //采购单完成
  454. $purchaseId = $order->purchaseId ?? 0;
  455. $purchase = PurchaseClass::getById($purchaseId, true);
  456. PurchaseClass::complete($purchase);
  457. }
  458. //获取订单信息 ssh 2019.11.28
  459. public static function getOrderList($where)
  460. {
  461. $data = self::getList('*', $where, 'addTime DESC');
  462. if (isset($data['list']) == false || empty($data['list'])) {
  463. return $data;
  464. }
  465. $orderSns = array_column($data['list'], 'orderSn');
  466. //获取订单的商品信息
  467. $goodsList = OrderGoodsService::getGoodsListByOrderSns($orderSns);
  468. //获取订花人的用户信息
  469. $orderUserIds = array_column($data['list'], 'userId');
  470. $orderUserIds = array_unique($orderUserIds);
  471. $userInfoList = UserService::getUserByIds($orderUserIds);
  472. $sjIdList = array_column($data['list'], 'sjId');
  473. $sjIdList = array_filter(array_unique($sjIdList));
  474. $merchantList = MerchantService::getByIds($sjIdList, null, 'id');
  475. $periodData = [0 => '上午', 1 => '下午', 2 => '晚上'];
  476. foreach ($data['list'] as $key => $val) {
  477. $id = $val['id'];
  478. $userId = $val['userId'];
  479. $data['list'][$key]['goodsInfoList'] = isset($goodsList[$id]) ? $goodsList[$id] : [];
  480. $data['list'][$key]['bookAvatar'] = isset($userInfoList[$userId]['smallAvatarUrl']) ? $userInfoList[$userId]['smallAvatarUrl'] : '';
  481. $sjId = $val['sjId'];
  482. $data['list'][$key]['name'] = isset($merchantList[$sjId]['name']) ? $merchantList[$sjId]['name'] : '';
  483. $data['list'][$key]['reachTime'] = OrderClass::getReachTime($val);
  484. $requireReachTime = '';
  485. if ($val['reachDate'] != '0000-00-00') {
  486. $reachPeriodId = $val['reachPeriod'];
  487. $reachPeriod = isset($periodData[$reachPeriodId]) ? $periodData[$reachPeriodId] : '上午';
  488. $requireReachTime = date("m-d", strtotime($val['reachDate'])) . ' ' . $reachPeriod;
  489. }
  490. $data['list'][$key]['requireReachTime'] = $requireReachTime;
  491. }
  492. return $data;
  493. }
  494. //取客户最近若干条订单 ssh 2019.11.30
  495. public static function getUserOrder($userId, $limit = 10, $order = null, $with = null)
  496. {
  497. $where = ['userId' => $userId, 'payStatus' => 1];
  498. return self::getLimitList('*', $where, $limit, $order, $with);
  499. }
  500. //支付前验证订单 ssh 2019.12.6
  501. public static function checkBeforePay($order, $userId)
  502. {
  503. $now = time();
  504. if (empty($order)) {
  505. util::fail('订单无效');
  506. }
  507. if (isset($order['userId']) && $order['userId'] != $userId) {
  508. util::fail('非法访问');
  509. }
  510. if ($order['status'] != 0) {
  511. util::fail('订单不是待付款状态');
  512. }
  513. if (isset($order['deadline']) && $now > $order['deadline']) {
  514. util::fail('订单已过期');
  515. }
  516. if (ceil($order['actPrice']) <= 0) {
  517. util::fail('订单金额有问题');
  518. }
  519. if ($order['payStatus'] == 1) {
  520. util::fail('您已经付过了');
  521. }
  522. }
  523. //商家验证是否有效 ssh 2019.12.15
  524. public static function valid($order, $shopId)
  525. {
  526. $now = date("Y-m-d H:i:s");
  527. if (empty($order)) {
  528. util::fail('订单无效');
  529. }
  530. if ($order['shopId'] != $shopId) {
  531. util::fail('不是您的订单');
  532. }
  533. if (isset($order['status']) && $order['status'] == OrderClass::ORDER_STATUS_UN_PAY) {
  534. if (isset($order['deadline']) && $now > $order['deadline']) {
  535. util::fail('订单已过期');
  536. }
  537. }
  538. }
  539. //点评 ssh 2019.12.16
  540. public static function comment($data)
  541. {
  542. return OrderClass::comment($data);
  543. }
  544. //订单归类
  545. public static function classify($id, $categoryId, $usageId)
  546. {
  547. $connection = Yii::$app->db;
  548. $transaction = $connection->beginTransaction();
  549. try {
  550. $order = OrderClass::getById($id, true);
  551. if ($order->payStatus != 1) {
  552. util::fail('订单还未付款');
  553. }
  554. if ($order->classify == 1) {
  555. util::fail('已经归类过了');
  556. }
  557. $order->categoryId = $categoryId;
  558. $order->usageId = $usageId;
  559. $order->classify = 1;
  560. $order->save();
  561. //设置流水的分类和用途并分配资金
  562. $setData = [
  563. 'sjId' => $order->sjId,
  564. 'capitalId' => $order->capitalId,
  565. 'time' => $order->payTime,
  566. 'usageId' => $order->usageId,
  567. 'categoryId' => $order->categoryId,
  568. 'amount' => $order->actPrice,
  569. 'payWay' => $order->payWay,
  570. ];
  571. SjCapitalService::orderClassify($setData);
  572. $transaction->commit();
  573. } catch (Exception $e) {
  574. $transaction->rollBack();
  575. util::fail('没有归类成功');
  576. }
  577. }
  578. //更新配送单 ssh 2019.12.17
  579. public static function updateSheet($order, $data)
  580. {
  581. $id = $order['id'];
  582. if ($order['status'] != OrderClass::ORDER_STATUS_UN_SEND) {
  583. util::fail('订单当前不是待配送状态');
  584. }
  585. $data['deliver'] = 1;
  586. OrderClass::updateById($id, $data);
  587. }
  588. //订单详情 ssh 2020.1.2
  589. public static function getOrderById($id)
  590. {
  591. return OrderClass::getOrderById($id);
  592. }
  593. //查看订单详情 ssh 2020.1.4
  594. public static function getOrderBySn($orderSn)
  595. {
  596. return OrderClass::getOrderBySn($orderSn);
  597. }
  598. //计算优惠 ssh 2020.3.9
  599. public static function getDiscountPrice($params)
  600. {
  601. $couponId = isset($params['couponId']) ? $params['couponId'] : 0;
  602. $userId = isset($params['userId']) ? $params['userId'] : 0;
  603. $price = isset($params['price']) ? $params['price'] : 0;
  604. $sourceType = isset($params['sourceType']) ? $params['sourceType'] : 0;
  605. $discountAmount = 0;
  606. $discountType = 0;
  607. $discountPrice = $price;
  608. //使用优惠券
  609. if (!empty($couponId)) {
  610. $discountAmount = CouponService::checkBeforeUse($couponId, $price, $userId);
  611. $discountPrice = stringUtil::calcSub($price, $discountAmount);
  612. $discountType = 1;
  613. } else {
  614. //使用会员
  615. // $asset = UserAssetClass::getByUserId($userId);
  616. // if (isset($asset['member']) && $asset['member'] > 0) {
  617. // $currentDiscount = $asset['discount'];
  618. // $currentPrice = $price * $currentDiscount;
  619. // $newPrice = substr(sprintf("%.3f", $currentPrice), 0, -1);
  620. // $discountType = 2;
  621. // $discountAmount = stringUtil::calcSub($price, $newPrice);
  622. // $discountPrice = $newPrice;
  623. // }
  624. }
  625. //如果没有优惠券和会员优惠,并且是朋友圈收款,则进行随机优惠 ssh 2019.9.12
  626. if ($discountPrice == $price && $sourceType == 3) {
  627. $discountAmount = OrderClass::getRandDiscount($price);
  628. //开发和测试环境直接随机优惠
  629. $env = getenv('YII_ENV');
  630. if ($env == 'local' || $env == 'test') {
  631. $discountAmount = 0.01;
  632. }
  633. $discountPrice = stringUtil::calcSub($price, $discountAmount);
  634. $discountType = 3;
  635. }
  636. $discountType = $discountAmount <= 0 ? 0 : $discountType;
  637. $discountPrice = $discountPrice <= 0 ? 0.01 : $discountPrice;
  638. return ['price' => $discountPrice, 'discountType' => $discountType, 'discountAmount' => $discountAmount];
  639. }
  640. //获取订单详情 ssh 2021.1.21
  641. public static function getOrderInfo($id, $showButton = false, $showItem = false, $showExpress = false, $isList = false)
  642. {
  643. $order = OrderClass::getById($id, true);
  644. if (empty($order)) {
  645. util::fail('没有找到订单');
  646. }
  647. $info = $order->attributes;
  648. $respond = OrderClass::groupOrder([$info], $showButton, $showItem, $showExpress, $isList);
  649. $result = current($respond);
  650. $orderId = intval($result['id'] ?? 0);
  651. $result['orderClearList'] = $orderId > 0 ? OrderCgClearClass::listPaidClearRecordsForOrder($orderId) : [];
  652. return $result;
  653. }
  654. //订单失效后的操作 ssh 2021.5.14
  655. public static function expire($order)
  656. {
  657. $purchaseId = $order->purchaseId ?? 0;
  658. $current = PurchaseClass::getById($purchaseId, true);
  659. if (empty($current)) {
  660. util::fail('没有找到采购单');
  661. }
  662. PurchaseService::expire($current);
  663. return true;
  664. }
  665. /**
  666. * 销售单付款成功后流程。
  667. * $isPurchase 为零售端采购支付时会引起销售方余额变动。
  668. * 空 sendTime 写成当前时刻,与采购单 PurchaseService::payAfter 对称。
  669. */
  670. public static function payAfter($order, $payWay, $isPurchase = false)
  671. {
  672. if (isset($order->status) == false) {
  673. util::fail('没有订单状态');
  674. }
  675. if ($order->status != OrderClass::ORDER_STATUS_UN_PAY) {
  676. util::fail('重复操作');
  677. }
  678. if (isset($order->debt) == false) {
  679. util::fail('订单有问题,没有欠款项');
  680. }
  681. $orderId = $order->id;
  682. $orderSn = $order->orderSn ?? '';
  683. $customId = $order->customId;
  684. $ghs = null;
  685. if (in_array($payWay, [dict::getDict('payWay', 'balancePay'), dict::getDict('payWay', 'debtPay')], true)) {
  686. $pair = GhsRechargeSettleService::lockAccountPair(['id' => $customId]);
  687. $custom = $pair['custom'];
  688. $ghs = $pair['ghs'];
  689. } else {
  690. $custom = CustomClass::getLockById($customId);
  691. }
  692. if (empty($custom)) {
  693. util::fail('没有找到客户信息');
  694. }
  695. //门店资产变化
  696. $amount = $order->actPrice ?? 0.00;
  697. $realPrice = $order->realPrice ?? 0.00;
  698. $currentShopId = $order->shopId ?? 0;
  699. $book = $order->book ?? 0;
  700. $shop = ShopClass::getLockById($currentShopId);
  701. if (empty($shop)) {
  702. util::fail('没有找到门店5');
  703. }
  704. if ($payWay == dict::getDict('payWay', 'debtPay') && $book != 1) {
  705. if (isset($shop->overAllowDebt) && $shop->overAllowDebt == 0) {
  706. $debtLimit = $custom->debtLimit ?? 0;
  707. $outstanding = \bizGhs\custom\classes\AccountMoneyClass::getOutstandingDebt($custom);
  708. $newDebt = bcadd((string)$realPrice, (string)$outstanding, 2);
  709. if (bccomp($newDebt, (string)$debtLimit, 2) > 0) {
  710. util::fail('待结款已超' . $debtLimit . ',请先结账');
  711. }
  712. }
  713. }
  714. $mainId = $shop->mainId ?? 0;
  715. $main = MainClass::getLockById($mainId);
  716. if (empty($main)) {
  717. util::fail('没有main信息3');
  718. }
  719. $main->unPayOrder -= 1;
  720. //预订单的待发货订单增加
  721. if ($book == 1) {
  722. $main->unSendOrder += 1;
  723. //预订单统计数量增加
  724. $itemList = OrderItemClass::getAllByCondition(['orderSn' => $orderSn], null, '*', null, true);
  725. if (!empty($itemList)) {
  726. $bookData = [];
  727. foreach ($itemList as $currentItem) {
  728. $itemRemark = $currentItem->remark ?? 0;
  729. $hasRemark = !empty($itemRemark) ? 1 : 0;
  730. $bookData[] = [
  731. 'productId' => $currentItem->productId,
  732. 'num' => $currentItem->xhNum ?? 0,
  733. 'lastNum' => $currentItem->xhNum ?? 0,
  734. 'remark' => $itemRemark,
  735. 'hasRemark' => $hasRemark,
  736. ];
  737. }
  738. $staffId = $order->shopAdminId ?? 0;
  739. $staffName = $order->shopAdminName ?? '';
  740. $params = [
  741. 'staffId' => $staffId, 'staffName' => $staffName, 'action' => 'add'
  742. ];
  743. BookItemClass::addBatchItem($bookData, $order, $params);
  744. }
  745. } else {
  746. $main->finishOrder += 1;
  747. }
  748. $main->totalOrderNum += 1;
  749. $currentTotalIncome = bcadd($main->totalIncome, $amount, 2);
  750. $main->totalIncome = $currentTotalIncome;
  751. $totalSale = bcadd($main->totalSale, $amount, 2);
  752. $main->totalSale = $totalSale;
  753. if ($payWay == dict::getDict('payWay', 'debtPay')) {
  754. $currentGathering = bcadd($main->mayGathering, $realPrice, 2);
  755. $main->mayGathering = $currentGathering;
  756. if ($custom->debtNum == 0) {
  757. $main->mayGatheringNum += 1;
  758. }
  759. }
  760. $main->save();
  761. $capitalType = dict::getDict('capitalType', 'xhGhsOrder', 'id');
  762. $shopAdminId = $order->shopAdminId ?? 0;
  763. $shopAdminName = $order->shopAdminName ?? '';
  764. $event = '下单';
  765. if ($isPurchase) {
  766. $event = '客户采购';
  767. if (isset($custom->name) && !empty($custom->name)) {
  768. $event = $custom->name . '采购';
  769. }
  770. }
  771. $sjId = $order->sjId ?? 0;
  772. $shopId = $order->shopId ?? 0;
  773. $capital = [
  774. 'capitalType' => $capitalType,
  775. 'relateId' => $orderId,
  776. 'io' => 1,
  777. 'amount' => $amount,
  778. 'totalIncome' => $currentTotalIncome,
  779. 'payWay' => $payWay,
  780. 'event' => $event,
  781. 'mainId' => $mainId,
  782. 'sjId' => $sjId,
  783. 'shopId' => $shopId,
  784. 'shopAdminId' => $shopAdminId,
  785. 'shopAdminName' => $shopAdminName,
  786. 'mainId' => $mainId,
  787. ];
  788. ShopCapitalClass::addCapital($capital);
  789. //客户采购,会增加余额,会增加收入
  790. if ($isPurchase == true) {
  791. //余额支付和在线支付(微信支付、支付宝支付)会增加余额
  792. if ($order->onlinePay == dict::getDict('onlinePay', 'yes') || $payWay == dict::getDict('payWay', 'balancePay')) {
  793. ShopClass::customKdAddBalance($main, $shop, $realPrice, $order, $capitalType);
  794. }
  795. }
  796. //现金付款,现金余额需要增加
  797. if ($payWay == dict::getDict('payWay', 'cash')) {
  798. $moneyBalance = bcadd($main->money, $realPrice, 2);
  799. $main->money = $moneyBalance;
  800. $main->save();
  801. $moneyEvent = $custom->name . "现金下单" . floatval($realPrice) . "元(单号 {$orderSn})";
  802. $moneyRemark = '';
  803. $change = [
  804. 'relateId' => $orderId,
  805. 'amount' => $realPrice,
  806. 'balance' => $moneyBalance,
  807. 'io' => 1,
  808. 'mainId' => $mainId,
  809. 'capitalType' => $capitalType,
  810. 'ptStyle' => dict::getDict('ptStyle', 'ghs'),
  811. 'event' => $moneyEvent,
  812. 'remark' => $moneyRemark,
  813. ];
  814. ShopMoneyChangeClass::addData($change);
  815. }
  816. //更新订单基础状态和支付状态
  817. $order->payWay = $payWay;
  818. if ($book == 1) {
  819. $order->status = OrderClass::ORDER_STATUS_UN_SEND;
  820. } else {
  821. $order->status = OrderClass::ORDER_STATUS_UN_SEND;
  822. }
  823. if ($payWay == dict::getDict('payWay', 'debtPay')) {
  824. if (isset($order->actPrice) && $order->actPrice == 0) {
  825. $order->debt = OrderClass::DEBT_NO;
  826. } else {
  827. $order->debt = OrderClass::DEBT_YES;
  828. }
  829. $order->remainDebtPrice = $order->orderPrice ?? 0;
  830. $order->debtPrice = $order->orderPrice ?? 0;
  831. }
  832. $order->payStatus = 1;
  833. $payTime = date("Y-m-d H:i:s");
  834. if (!isset($order->payTime) || $order->payTime == '0000-00-00 00:00:00') {
  835. $order->payTime = $payTime;
  836. }
  837. // 付款成功后补发货时间:空 sendTime 写成当前时刻,与采购单 payAfter 对称。开单已写入的不覆盖。
  838. $sendTime = $order->sendTime ?? '';
  839. if (empty($sendTime) || strpos((string)$sendTime, '0000-00-00') === 0) {
  840. $order->sendTime = $payTime;
  841. }
  842. $order->save();
  843. //新订单进入队列,用于花材的销量统计
  844. OrderClass::newOrderAddQueue($order);
  845. //员工业绩、总业绩增加
  846. $shopAdminId = $order->shopAdminId ?? 0;
  847. if (!empty($shopAdminId)) {
  848. $shopAdmin = ShopAdminClass::getById($shopAdminId, true);
  849. $shopAdmin->kdAmount = bcadd($shopAdmin->kdAmount, $amount, 2);
  850. $shopAdmin->save();
  851. $yjAmount = $order->orderPrice ?? 0;
  852. StatYjClass::addYj($main, $shop, $shopAdminId, $yjAmount);
  853. }
  854. //余额支付
  855. if ($payWay == dict::getDict('payWay', 'balancePay') && $isPurchase == false) {
  856. CustomClass::payToChangeBalance($order, $custom, $ghs);
  857. }
  858. //欠款
  859. if ($payWay == dict::getDict('payWay', 'debtPay')) {
  860. CustomClass::cgDebtAmountAdd($custom, $order, false, $ghs);
  861. }
  862. $custom->buyNum += 1;
  863. $custom->buyAmount = bcadd($custom->buyAmount, $amount, 2);
  864. $custom->visitTime = date("Y-m-d H:i:s");
  865. $custom->save();
  866. //收入统计
  867. StatIncomeClass::updateOrInsert($main, $shop, $amount);
  868. //各渠道收入金额统计
  869. StatKdClass::replace($shop, $amount, $payWay);
  870. //今日订单+1
  871. StatOrderClass::updateOrInsert($main, $shop);
  872. //销售统计
  873. StatSaleClass::replace($main, $shop, $amount);
  874. //客户采购统计
  875. StatKhCgClass::replace($order, $amount);
  876. //当天同个人同地址的订单汇总
  877. $prefixDate = substr($order->payTime, 0, 10);
  878. OrderClass::updateSameTimeIds($order, $prefixDate);
  879. OrderClass::updateMergeOrder($order, 0);
  880. }
  881. //发起退款
  882. public static function refund($id, $post)
  883. {
  884. $order = OrderClass::getLockById($id);
  885. if (empty($order)) {
  886. util::fail('没有找到订单');
  887. }
  888. // 未完成订单:先确认送达/完成,再继续售后(前端不再预确认)
  889. $order = OrderClass::ensureCompletedForRefund($order, $post);
  890. // 无支付时间不能售后
  891. \bizGhs\order\classes\NextDayRefundClass::assertCanRefund($order);
  892. $sameDay = intval($post['sameDay'] ?? \bizGhs\order\classes\RefundOrderClass::SAME_DAY_YES);
  893. // 非当天 / 已结账 / 挂账结清 → 强制跨天售后(sameDay=0),不再拦截为「不能售后」
  894. if (\bizGhs\order\classes\NextDayRefundClass::mustUseNextDay($order)) {
  895. $sameDay = \bizGhs\order\classes\RefundOrderClass::SAME_DAY_NO;
  896. $post['sameDay'] = $sameDay;
  897. }
  898. // 兜底:仍写成当天且已结账时拦截(正常不会走到,mustUseNextDay 已覆盖)
  899. if ($sameDay === \bizGhs\order\classes\RefundOrderClass::SAME_DAY_YES
  900. && isset($order->clearId) && !empty($order->clearId)) {
  901. util::fail('已经结账,请按冲销处理');
  902. }
  903. if ($order->debt != 1 && $order->addTime < '2023-10-17 00:00:00') {
  904. util::fail('10月17日前订单无法申请退款');
  905. }
  906. //供货商销售单退款
  907. $saleRefund = RefundOrderService::addRefund($post, $order);
  908. $saleRefundId = $saleRefund->id;
  909. $purchaseId = $order->purchaseId;
  910. $cg = PurchaseClass::getLockById($purchaseId);
  911. if (empty($cg)) {
  912. util::fail('没有找到采购订单');
  913. }
  914. $mainId = $cg->mainId ?? 0;
  915. //花材列表结构
  916. // [{productId:0,num:1,unitType:0,unitPrice:12,unitName:'扎'}] productId 花材id num 退货数 unitType 大小单位0大1小 unitPrice 售价 unitName单位名称
  917. //供货商的花材信息转换成零售店的花材信息
  918. $ghsProduct = $post['product'] ?? '';
  919. $ghsProductIndex = array_column($ghsProduct, null, 'productId');
  920. $ids = array_column($ghsProduct, 'productId');
  921. $ghsProductInfo = ProductClass::getByIds($ids, null, 'itemId');
  922. $ptItemIds = array_keys($ghsProductInfo);
  923. $ptItemIds = array_unique(array_filter($ptItemIds));
  924. $hdProductInfo = ProductClass::getAllByCondition(['mainId' => $mainId, 'itemId' => ['in', $ptItemIds]], null, '*', 'itemId');
  925. $newProduct = [];
  926. foreach ($hdProductInfo as $ptItemId => $hdInfo) {
  927. $ghsItemId = $ghsProductInfo[$ptItemId]['id'] ?? 0;
  928. $current = $ghsProductIndex[$ghsItemId] ?? [];
  929. $hdItemId = $hdInfo['id'] ?? 0;
  930. $cost = $hdInfo['cost'] ?? 0;
  931. $name = $hdInfo['name'] ?? '';
  932. $cover = $hdInfo['cover'] ?? '';
  933. $ratio = $hdInfo['ratio'] ?? 20;
  934. if (empty($current)) {
  935. util::fail('没有找到花材');
  936. }
  937. $current['productId'] = $hdItemId;
  938. $current['ptItemId'] = $ptItemId;
  939. $current['cost'] = $cost;
  940. $current['name'] = $name;
  941. $current['cover'] = $cover;
  942. $current['ratio'] = $ratio;
  943. $newProduct[] = $current;
  944. }
  945. $refundType = $post['refundType'] ?? 0;
  946. $price = $post['price'] ?? 0;
  947. $remark = $post['remark'] ?? '';
  948. $cause = $post['cause'] ?? 0;
  949. $refundSendCost = isset($post['refundSendCost']) && is_numeric($post['refundSendCost']) ? $post['refundSendCost'] : 0;
  950. $refundPackCost = isset($post['refundPackCost']) && is_numeric($post['refundPackCost']) ? $post['refundPackCost'] : 0;
  951. $cgData = [
  952. 'product' => $newProduct,
  953. 'refundType' => $refundType,
  954. 'price' => $price,
  955. 'remark' => $remark,
  956. 'cause' => $cause,
  957. 'refundSendCost' => $refundSendCost,
  958. 'refundPackCost' => $refundPackCost,
  959. // 与销售侧售后成对写入 sameDay + 支付快照 + 资金三态
  960. 'sameDay' => $saleRefund->sameDay ?? \bizGhs\order\classes\RefundOrderClass::SAME_DAY_YES,
  961. 'onlinePay' => $saleRefund->onlinePay ?? 0,
  962. 'payWay' => $saleRefund->payWay ?? 0,
  963. 'hasReturn' => $saleRefund->hasReturn ?? 0,
  964. 'couldReturn' => $saleRefund->couldReturn ?? 0,
  965. 'returnBalance' => $saleRefund->returnBalance ?? 0,
  966. ];
  967. $cgRefund = CgRefundService::addRefund($cgData, $cg);
  968. $cgRefundId = $cgRefund->id;
  969. $saleRefund->cgRefundId = $cgRefundId;
  970. $saleRefund->save();
  971. $cgRefund->saleRefundId = $saleRefundId;
  972. $cgRefund->sameDay = intval($saleRefund->sameDay ?? 1);
  973. $cgRefund->onlinePay = intval($saleRefund->onlinePay ?? 0);
  974. $cgRefund->payWay = intval($saleRefund->payWay ?? 0);
  975. $cgRefund->hasReturn = intval($saleRefund->hasReturn ?? 0);
  976. $cgRefund->couldReturn = intval($saleRefund->couldReturn ?? 0);
  977. $cgRefund->returnBalance = intval($saleRefund->returnBalance ?? 0);
  978. $cgRefund->save(false, ['saleRefundId', 'sameDay', 'onlinePay', 'payWay', 'hasReturn', 'couldReturn', 'returnBalance']);
  979. return $saleRefund;
  980. }
  981. /**
  982. * 创建冲销单(金额为负的独立订单)
  983. * 设计说明:不复用极其庞杂的常规开单主链路(createNewOrder->OrderClass::addOrder),
  984. * 避免误伤现网所有商户的正常开单流程;只在数据落地时保持与正常订单相同的表结构,方便统一查询/统计。
  985. * @param $post array 前端提交参数:customId,product(JSON数组),remark,needPrint,payWay,forwardStock,returnBalance,
  986. * originalRefund(可选,原路退回网关,仅线上支付+关联原单时生效),relateOrderId(可选),refundOrderId(可选)
  987. * @param $custom array 客户信息(CustomClass::getCustom 返回)
  988. * @return array ['order'=>冲销单模型, 'customBalance'=>返充后的余额(未返充时为null), 'thirdRefundNo'=>原路退回的网关流水号(未走原路退回时为空)]
  989. */
  990. public static function createForwardOrder($post, $custom)
  991. {
  992. $customId = $custom['id'] ?? 0;
  993. $mainId = $post['mainId'] ?? 0;
  994. $shopId = $post['shopId'] ?? 0;
  995. $sjId = $post['sjId'] ?? 0;
  996. $shopAdminId = $post['shopAdminId'] ?? 0;
  997. $shopAdminName = $post['shopAdminName'] ?? '';
  998. $ghsId = $custom['ghsId'] ?? 0;
  999. //售后申请审核转冲销单场景:refundOrderId 是待审核的售后申请(xhRefund)id,
  1000. //由 refundDetail.vue"通过"弹框直接提交而来,商品明细以售后申请里已提交的为准(不信任前端重新传的product),
  1001. //避免和客户/花店实际申请的售后商品不一致;这里锁行是因为最后要把它标记为"已通过"。ssh 冲销单功能
  1002. $refundOrderId = $post['refundOrderId'] ?? 0;
  1003. $refundOrder = null;
  1004. if (!empty($refundOrderId)) {
  1005. $refundOrder = RefundOrderClass::getLockById($refundOrderId);
  1006. if (empty($refundOrder)) {
  1007. util::fail('没有找到售后申请');
  1008. }
  1009. if ($refundOrder->mainId != $mainId) {
  1010. util::fail('售后申请信息不匹配');
  1011. }
  1012. if ($refundOrder->status != 0) {
  1013. util::fail('该售后申请已被处理,请刷新页面');
  1014. }
  1015. if ($refundOrder->customId != $customId) {
  1016. util::fail('售后申请客户信息不匹配');
  1017. }
  1018. $relateOrderSnFromRefund = $refundOrder->relateOrderSn ?? '';
  1019. $relateOrderFromRefund = OrderClass::getByCondition(['orderSn' => $relateOrderSnFromRefund], true);
  1020. if (empty($relateOrderFromRefund)) {
  1021. util::fail('没有找到售后申请关联的原订单');
  1022. }
  1023. $post['relateOrderId'] = $relateOrderFromRefund->id;
  1024. $refundItems = RefundOrderItemClass::getAllByCondition(['orderSn' => $refundOrder->orderSn], null, '*');
  1025. $productList = [];
  1026. foreach ($refundItems as $refundItem) {
  1027. $productList[] = [
  1028. 'productId' => $refundItem['productId'] ?? 0,
  1029. 'bigNum' => ($refundItem['xhUnitType'] ?? 0) == 0 ? ($refundItem['xhNum'] ?? 0) : 0,
  1030. 'smallNum' => ($refundItem['xhUnitType'] ?? 0) == 1 ? ($refundItem['xhNum'] ?? 0) : 0,
  1031. 'price' => $refundItem['xhUnitPrice'] ?? 0,
  1032. ];
  1033. }
  1034. } else {
  1035. $productList = $post['product'] ?? [];
  1036. }
  1037. if (empty($productList)) {
  1038. util::fail('请选择花材');
  1039. }
  1040. $level = $custom['level'] ?? 1;
  1041. $live = isset($custom['live']) ? $custom['live'] : 1;
  1042. $respond = ProductClass::formatProductInfo($productList, $level, $live, ['custom' => $custom]);
  1043. $items = $respond['product'] ?? [];
  1044. if (empty($items)) {
  1045. util::fail('花材信息有误');
  1046. }
  1047. //本次冲销总金额(正数)
  1048. $totalAmount = 0;
  1049. foreach ($items as $it) {
  1050. $totalAmount = bcadd($totalAmount, $it['price'], 2);
  1051. }
  1052. if (bccomp($totalAmount, '0', 2) <= 0) {
  1053. util::fail('冲销金额必须大于0');
  1054. }
  1055. $forwardStock = isset($post['forwardStock']) && in_array((int)$post['forwardStock'], [0, 1], true) ? (int)$post['forwardStock'] : 1;
  1056. $returnBalance = isset($post['returnBalance']) && (int)$post['returnBalance'] == 1 ? 1 : 0;
  1057. //原路退回(仅线上支付关联原单场景可用):是否调支付网关把这笔钱退回给付款人,与"返充到余额"二选一。ssh 冲销单功能
  1058. $originalRefund = isset($post['originalRefund']) && (int)$post['originalRefund'] == 1 ? 1 : 0;
  1059. $payWay = isset($post['payWay']) ? (int)$post['payWay'] : dict::getDict('payWay', 'wxPay');
  1060. //关联原单场景:累计冲销上限校验,并且强制按原单支付方式,不信任前端传的 payWay
  1061. $relateOrderId = $post['relateOrderId'] ?? 0;
  1062. $relateOrder = null;
  1063. $relateCg = null;
  1064. if (!empty($relateOrderId)) {
  1065. $relateOrder = OrderClass::getLockById($relateOrderId);
  1066. if (empty($relateOrder)) {
  1067. util::fail('没有找到原订单');
  1068. }
  1069. if ($relateOrder->mainId != $mainId || $relateOrder->customId != $customId) {
  1070. util::fail('原订单信息不匹配');
  1071. }
  1072. //服务端复核"转冲销单"资格:非当天下单 或者 欠款已结清,二者满足其一才允许,不能只靠前端拦截,
  1073. //避免绕过前端直接调接口对"今天下的、欠款还没结清"的订单发起冲销单。ssh 冲销单功能
  1074. $relatePayTime = $relateOrder->payTime ?? '';
  1075. $notToday = empty($relatePayTime) || date('Y-m-d', strtotime($relatePayTime)) != date('Y-m-d');
  1076. $debtCleared = bccomp($relateOrder->debtPrice ?? 0, '0', 2) > 0 && bccomp($relateOrder->remainDebtPrice ?? 0, '0', 2) == 0;
  1077. if (!$notToday && !$debtCleared) {
  1078. util::fail('该订单不满足转冲销单条件(需为非当天订单,或欠款已结清),请走常规售后流程');
  1079. }
  1080. $actPrice = $relateOrder->actPrice ?? 0;
  1081. $hasForwardPrice = $relateOrder->forwardPrice ?? 0;
  1082. if (bccomp($hasForwardPrice, $actPrice, 2) >= 0) {
  1083. util::fail('该订单已冲销完,无法再次冲销');
  1084. }
  1085. $remain = bcsub($actPrice, $hasForwardPrice, 2);
  1086. if (bccomp($totalAmount, $remain, 2) == 1) {
  1087. util::fail("冲销金额超过原订单可冲销余额(剩余可冲销 ¥{$remain})");
  1088. }
  1089. //退款方式锁定为原单实际支付方式
  1090. $payWay = $relateOrder->payWay ?? $payWay;
  1091. //花店侧原采购单:下面"原路退回"网关退款、以及后面同步hasForward/forwardPrice都要用到,这里统一加锁取一次
  1092. $relateCgId = $relateOrder->purchaseId ?? 0;
  1093. if (!empty($relateCgId)) {
  1094. $relateCg = PurchaseClass::getLockById($relateCgId);
  1095. }
  1096. }
  1097. //资金处理方式按原单支付渠道强制/校验,不完全信任前端传值:仅在"售后转冲销单"(带relateOrderId)场景生效,
  1098. //因为只有这种场景 payWay 才是真实原订单的支付渠道;没有关联原单的自由开冲销单,payWay 只是用户自己选的
  1099. //一个记账渠道说明,并不对应任何真实交易,不需要也不能做这里的强制/原路退回逻辑。
  1100. //挂账、余额支付:欠款本质是负余额,返充余额=冲减欠款,二者在账本层面是同一个动作,强制走"返充余额",不允许原路退回网关
  1101. //微信/支付宝:允许"原路退回"(真实网关退款)与"返充余额"二选一
  1102. if (!empty($relateOrderId)) {
  1103. $wxPay = dict::getDict('payWay', 'wxPay');
  1104. $aliPay = dict::getDict('payWay', 'alipay');
  1105. $debtPay = dict::getDict('payWay', 'debtPay');
  1106. $balancePay = dict::getDict('payWay', 'balancePay');
  1107. if (in_array($payWay, [$debtPay, $balancePay], true)) {
  1108. $returnBalance = 1;
  1109. $originalRefund = 0;
  1110. } elseif (in_array($payWay, [$wxPay, $aliPay], true)) {
  1111. if ($originalRefund == 1) {
  1112. if (empty($relateCg)) {
  1113. util::fail('没有关联原订单的真实交易信息,无法原路退回');
  1114. }
  1115. //原路退回和返充余额二选一,不能同时生效
  1116. $returnBalance = 0;
  1117. }
  1118. } else {
  1119. $originalRefund = 0;
  1120. }
  1121. } else {
  1122. //自由开冲销单:不存在"原路退回网关"这个概念,永远不生效,防止前端异常传参
  1123. $originalRefund = 0;
  1124. }
  1125. //落库统一取负:金额字段为负,数量字段保持正数(代表冲销的数量)
  1126. $negItems = [];
  1127. $totalCost = 0;
  1128. foreach ($items as $it) {
  1129. $it['price'] = bcmul($it['price'], -1, 2);
  1130. $it['userPrice'] = bcmul($it['userPrice'], -1, 2);
  1131. $it['unitPrice'] = bcmul($it['unitPrice'], -1, 2);
  1132. $it['smallUnitPrice'] = bcmul($it['smallUnitPrice'], -1, 2);
  1133. $it['xhPrice'] = bcmul($it['xhPrice'], -1, 2);
  1134. $it['xhUnitPrice'] = bcmul($it['xhUnitPrice'], -1, 2);
  1135. $it['xhPrePrice'] = bcmul($it['xhPrePrice'], -1, 2);
  1136. $it['xhPreUnitPrice'] = bcmul($it['xhPreUnitPrice'], -1, 2);
  1137. $it['cost'] = bcmul($it['cost'], -1, 2);
  1138. $it['forward'] = 1;
  1139. $totalCost = bcadd($totalCost, $it['cost'], 2);
  1140. $negItems[] = $it;
  1141. }
  1142. $actPrice = bcmul($totalAmount, -1, 2);
  1143. $snData = ['shopId' => $shopId, 'mainId' => $mainId, 'ghsId' => $ghsId, 'customId' => $customId];
  1144. $orderSn = orderSn::getGhsOrderSn($snData);
  1145. $now = date('Y-m-d H:i:s');
  1146. $deadline = date('Y-m-d H:i:s', time() + dict::getDict('order_pay_has_time'));
  1147. $orderData = [
  1148. 'mainId' => $mainId,
  1149. 'sendNum' => '',
  1150. 'orderSn' => $orderSn,
  1151. 'customId' => $customId,
  1152. 'customName' => $custom['name'] ?? '',
  1153. 'customNamePy' => $custom['py'] ?? '',
  1154. 'customAvatar' => $custom['shortSmallAvatar'] ?? '',
  1155. 'customMobile' => $custom['mobile'] ?? '',
  1156. 'ghsId' => $ghsId,
  1157. 'sjId' => $sjId,
  1158. 'shopId' => $shopId,
  1159. 'shopAdminId' => $shopAdminId,
  1160. 'shopAdminName' => $shopAdminName,
  1161. 'getStaffId' => $shopAdminId,
  1162. 'getStaffName' => $shopAdminName,
  1163. 'itemNum' => $respond['totalItemNum'] ?? 0,
  1164. 'bigNum' => $respond['bigNum'] ?? 0,
  1165. 'smallNum' => $respond['smallNum'] ?? 0,
  1166. 'itemPrice' => $actPrice,
  1167. 'prePrice' => $actPrice,
  1168. 'orderPrice' => $actPrice,
  1169. 'bookPrice' => $actPrice,
  1170. 'realPrice' => $actPrice,
  1171. 'actPrice' => $actPrice,
  1172. 'totalCost' => $totalCost,
  1173. 'remainCost' => $totalCost,
  1174. 'payWay' => $payWay,
  1175. 'fromType' => 1,
  1176. 'debt' => OrderClass::DEBT_NO,
  1177. 'status' => OrderClass::ORDER_STATUS_COMPLETE,
  1178. 'payStatus' => OrderClass::PAY_STATUS_HAS_PAY,
  1179. 'sendType' => OrderClass::SEND_TYPE_SHOP_GET,
  1180. 'sendStatus' => -4,
  1181. 'sendTimeWant' => date('Y-m-d'),
  1182. 'payTime' => $now,
  1183. 'deadline' => $deadline,
  1184. 'autoSetTime' => $deadline,
  1185. 'needPrint' => $post['needPrint'] ?? 2,
  1186. 'remark' => $post['remark'] ?? '',
  1187. 'forward' => 1,
  1188. 'forwardStock' => $forwardStock,
  1189. 'salt' => stringUtil::charsShuffleLowerCase(10),
  1190. ];
  1191. $order = OrderClass::add($orderData, true);
  1192. foreach ($negItems as $it) {
  1193. $it['orderSn'] = $orderSn;
  1194. $it['mainId'] = $mainId;
  1195. OrderItemClass::add($it, true);
  1196. }
  1197. //库存处理:forwardStock==0 货退回来则加库存,==1 不退回则不动库存
  1198. if ($forwardStock == 0) {
  1199. $customName = $custom['name'] ?? '';
  1200. foreach ($negItems as $it) {
  1201. $productId = $it['productId'] ?? 0;
  1202. $itemNum = $it['num'] ?? 0;
  1203. if (empty($productId) || bccomp($itemNum, '0', 2) <= 0) {
  1204. continue;
  1205. }
  1206. $stockInfo = ProductClass::addStockByItemNum($productId, $itemNum);
  1207. $recordData = [
  1208. 'sjId' => $sjId,
  1209. 'shopId' => $shopId,
  1210. 'mainId' => $mainId,
  1211. 'orderSn' => $orderSn,
  1212. 'itemId' => $it['itemId'] ?? 0,
  1213. 'productId' => $productId,
  1214. 'itemNum' => $itemNum,
  1215. 'oldStock' => $stockInfo['oldStock'] ?? 0,
  1216. 'newStock' => $stockInfo['newStock'] ?? 0,
  1217. 'relateName' => $customName,
  1218. 'ptStyle' => dict::getDict('ptStyle', 'ghs'),
  1219. 'io' => 1,
  1220. ];
  1221. StockRecordClass::ghsRefundAddRecord($recordData);
  1222. }
  1223. }
  1224. //配对生成花店侧采购冲销记录 xhCg/xhCgItem,直接构造模型保存,不复用 PurchaseClass::addPurchase 的完整业务规则
  1225. //此处需要加锁,因为下面还要用它同步扣减 xhGhs.expendAmount(累计消费镜像字段)
  1226. $ghs = GhsClass::getLockById($ghsId);
  1227. $cgSn = orderSn::getPurchaseSn($snData);
  1228. $cgData = [
  1229. 'mainId' => $ghs['ownMainId'] ?? 0,
  1230. 'saleId' => $order->id ?? 0,
  1231. 'orderSn' => $cgSn,
  1232. 'customId' => $customId,
  1233. 'ghsId' => $ghsId,
  1234. 'ghsName' => $ghs['name'] ?? '',
  1235. 'ghsAvatar' => $ghs['avatar'] ?? '',
  1236. 'ghsMobile' => $ghs['mobile'] ?? '',
  1237. 'ghsFullAddress' => $ghs['fullAddress'] ?? '',
  1238. 'sjId' => $custom['sjId'] ?? 0,
  1239. 'shopId' => $custom['shopId'] ?? 0,
  1240. 'ghsShopId' => $shopId,
  1241. 'ghsShopAdminId' => $shopAdminId,
  1242. 'ghsShopAdminName' => $shopAdminName,
  1243. 'itemPrice' => $actPrice,
  1244. 'prePrice' => $actPrice,
  1245. 'orderPrice' => $actPrice,
  1246. 'bookPrice' => $actPrice,
  1247. 'realPrice' => $actPrice,
  1248. 'actPrice' => $actPrice,
  1249. 'kindNum' => count($negItems),
  1250. 'bigNum' => $respond['bigNum'] ?? 0,
  1251. 'smallNum' => $respond['smallNum'] ?? 0,
  1252. 'payWay' => $payWay,
  1253. 'status' => PurchaseClass::STATUS_COMPLETE,
  1254. 'cgStyle' => dict::getDict('cgStyle', 'ghs'),
  1255. 'debt' => PurchaseClass::DEBT_NO,
  1256. 'getType' => PurchaseClass::GET_TYPE_SELF_GET,
  1257. 'payStatus' => 1,
  1258. 'sendTimeWant' => date('Y-m-d'),
  1259. 'payTime' => $now,
  1260. 'remark' => $post['remark'] ?? '',
  1261. 'forward' => 1,
  1262. 'forwardStock' => $forwardStock,
  1263. 'salt' => stringUtil::charsShuffleLowerCase(10),
  1264. ];
  1265. $cg = PurchaseClass::add($cgData, true);
  1266. $order->purchaseId = $cg->id ?? 0;
  1267. $order->save(false, ['purchaseId']);
  1268. foreach ($negItems as $it) {
  1269. $cgItemData = [
  1270. 'name' => $it['name'] ?? '',
  1271. 'cover' => $it['cover'] ?? '',
  1272. 'orderSn' => $cgSn,
  1273. 'productId' => $it['productId'] ?? 0,
  1274. 'itemId' => $it['itemId'] ?? 0,
  1275. 'mainId' => $cgData['mainId'],
  1276. 'ghsId' => $ghsId,
  1277. 'itemNum' => $it['num'] ?? 0,
  1278. 'preItemNum' => $it['num'] ?? 0,
  1279. 'itemInfo' => json_encode(['itemName' => $it['name'] ?? '', 'bigNum' => $it['bigNum'] ?? 0, 'smallNum' => $it['smallNum'] ?? 0]),
  1280. 'ghsProductId' => $it['productId'] ?? 0,
  1281. 'ratio' => $it['ratio'] ?? 0,
  1282. 'ratioType' => $it['ratioType'] ?? 0,
  1283. 'price' => $it['userPrice'] ?? 0,
  1284. 'totalPrice' => $it['price'] ?? 0,
  1285. 'preTotalPrice' => $it['price'] ?? 0,
  1286. 'variety' => $it['variety'] ?? 0,
  1287. 'unitWeight' => $it['unitWeight'] ?? 0,
  1288. 'belongCost' => $it['belongCost'] ?? 0,
  1289. 'remark' => $it['remark'] ?? '',
  1290. 'kind' => $it['kind'] ?? 0,
  1291. 'forward' => 1,
  1292. ];
  1293. PurchaseItemClass::add($cgItemData, true);
  1294. }
  1295. //累计消费金额同步:冲销单本质是负金额订单,$actPrice 已经是负数。
  1296. //xhGhsCustom.buyAmount 与 xhGhs.expendAmount 是同一份"累计消费"的两边镜像
  1297. //(参考 CustomController::actionRefreshBuyamount 按订单 actPrice 汇总重算两边字段的口径),
  1298. //这里直接按 actPrice 同步扣减,两边保持一致;但冲销单不算一次新的"下单",buyNum/买入次数不增加。
  1299. $lockCustom = CustomClass::getLockById($customId);
  1300. if (empty($lockCustom)) {
  1301. util::fail('没有找到客户');
  1302. }
  1303. $lockCustom->buyAmount = bcadd($lockCustom->buyAmount ?? '0.00', $actPrice, 2);
  1304. $lockCustom->save(false, ['buyAmount']);
  1305. $ghs->expendAmount = bcadd($ghs->expendAmount ?? '0.00', $actPrice, 2);
  1306. $ghs->save(false, ['expendAmount']);
  1307. //资金处理:originalRefund==1 调支付网关原路退回给付款人;returnBalance==1 加回客户余额;
  1308. //两者都为0时不改动任何资金字段,只把 payWay 记在订单上作为返款渠道记录。ssh 冲销单功能
  1309. $customBalance = null;
  1310. $thirdRefundNo = '';
  1311. if ($originalRefund == 1) {
  1312. //原路退回:用原单对应的采购单(xhCg)真实支付信息调网关退款,用冲销单自己的orderSn做退款流水号,天然保证唯一/幂等
  1313. $thirdRefundNo = PurchaseClass::forwardOriginalOnlineRefund($relateCg, $orderSn, $totalAmount, '冲销单' . $orderSn . '原路退回');
  1314. $order->remark = trim(($order->remark ?? '') . " [原路退回成功,网关退款单号:{$thirdRefundNo}]");
  1315. $order->save(false, ['remark']);
  1316. } elseif ($returnBalance == 1) {
  1317. $customBalance = CustomClass::forwardReturnBalance($lockCustom, $order, $totalAmount);
  1318. }
  1319. //关联原单:写关系表 + 同步原单 hasForward/forwardPrice 与订单项 forwardNum
  1320. if (!empty($relateOrderId) && !empty($relateOrder)) {
  1321. OrderForwardClass::addRelate($relateOrder, $order, $totalAmount);
  1322. $relateOrder->hasForward = 1;
  1323. $relateOrder->forwardPrice = bcadd($relateOrder->forwardPrice ?? 0, $totalAmount, 2);
  1324. $relateOrder->save(false, ['hasForward', 'forwardPrice']);
  1325. $relateItems = OrderItemClass::getAllByCondition(['orderSn' => $relateOrder->orderSn], null, '*', 'productId', true);
  1326. foreach ($negItems as $it) {
  1327. $productId = $it['productId'] ?? 0;
  1328. $relateItem = $relateItems[$productId] ?? null;
  1329. if (!empty($relateItem)) {
  1330. $relateItem->forward = 1;
  1331. $relateItem->forwardNum = bcadd($relateItem->forwardNum ?? 0, $it['num'] ?? 0, 2);
  1332. $relateItem->save(false, ['forward', 'forwardNum']);
  1333. }
  1334. }
  1335. //同步花店侧原采购单(xhCg)的 hasForward/forwardPrice 与采购单项(xhCgItem)的 forwardNum,
  1336. //供 hdApp 的 purDetails.vue/refundList.vue 展示冲销记录,与 GHS 侧原单缓存字段保持一致
  1337. //($relateCg 已在前面校验资金处理方式时统一加锁取过,这里直接复用,避免重复查询/重复加锁)
  1338. if (!empty($relateCg)) {
  1339. $relateCg->hasForward = 1;
  1340. $relateCg->forwardPrice = bcadd($relateCg->forwardPrice ?? 0, $totalAmount, 2);
  1341. $relateCg->save(false, ['hasForward', 'forwardPrice']);
  1342. $relateCgItems = PurchaseItemClass::getAllByCondition(['orderSn' => $relateCg->orderSn], null, '*', 'productId', true);
  1343. foreach ($negItems as $it) {
  1344. $productId = $it['productId'] ?? 0;
  1345. $relateCgItem = $relateCgItems[$productId] ?? null;
  1346. if (!empty($relateCgItem)) {
  1347. $relateCgItem->forward = 1;
  1348. $relateCgItem->forwardNum = bcadd($relateCgItem->forwardNum ?? 0, $it['num'] ?? 0, 2);
  1349. $relateCgItem->save(false, ['forward', 'forwardNum']);
  1350. }
  1351. }
  1352. }
  1353. }
  1354. //售后申请审核转冲销单:这条售后申请本身并没有走老的 passRefund 流程(没有原路退款/没有改原订单actPrice等),
  1355. //真正的退款/退货/返余额是由上面新生成的这张冲销单完成的,所以不能标"已通过"(会让人误以为是走了老流程原路退款)。
  1356. //这里标"已取消",并把冲销单号写进 hdRemark 备注,同时用 forwardOrderId 结构化记录对应的冲销单id,
  1357. //方便后续排查/统计这条售后申请最终去了哪(forwardOrderId=0表示常规售后,未转冲销单)。ssh 冲销单功能
  1358. if (!empty($refundOrder)) {
  1359. $refundOrder->status = RefundOrderClass::STATUS_CANCEL;
  1360. $refundOrder->forwardOrderId = $order->id;
  1361. $refundOrder->hdRemark = '已转为冲销单处理,退款/退货/返余额流程详见冲销单:' . $orderSn;
  1362. $refundOrder->shopAdminId = $shopAdminId;
  1363. $refundOrder->shopAdminName = $shopAdminName;
  1364. $refundOrder->save(false, ['status', 'forwardOrderId', 'hdRemark', 'shopAdminId', 'shopAdminName']);
  1365. //明细项同步标记为"已取消",与父级售后申请的status保持一致(不再是"已处理",因为并没有走老的passRefund流程去处理它)
  1366. $refundItemModels = RefundOrderItemClass::getAllByCondition(['orderSn' => $refundOrder->orderSn], null, '*', null, true);
  1367. foreach ($refundItemModels as $refundItemModel) {
  1368. $refundItemModel->status = RefundOrderClass::STATUS_CANCEL;
  1369. $refundItemModel->save(false, ['status']);
  1370. }
  1371. //同步花店端(hdApp)对应的售后记录(xhCgRefund):一条售后在GHS/HD两端各有一条记录(通过cgRefundId关联),
  1372. //两端都要能看出"这条售后已转冲销单",HD侧记录的是花店视角的冲销采购单id(xhCg.id)
  1373. $cgRefundId = $refundOrder->cgRefundId ?? 0;
  1374. if (!empty($cgRefundId)) {
  1375. $cgRefund = CgRefundClass::getLockById($cgRefundId);
  1376. if (!empty($cgRefund)) {
  1377. $cgRefund->forwardCgId = $cg->id ?? 0;
  1378. $cgRefund->hdRemark = '已转为冲销单处理,退款/退货/返余额流程详见冲销单:' . $cgSn;
  1379. $cgRefund->save(false, ['forwardCgId', 'hdRemark']);
  1380. }
  1381. }
  1382. }
  1383. return ['order' => $order, 'customBalance' => $customBalance, 'thirdRefundNo' => $thirdRefundNo];
  1384. }
  1385. }