NextDayRefundClass.php 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. <?php
  2. /**
  3. * 跨天售后业务类(biz-ghs/order/classes)
  4. * 用途:资格校验、支付快照与资金三态、隔天金额/返余额、统计净销量辅助。
  5. * 统计通过日只认真实 passTime,空/0000-00-00 视为未通过并剔除,不回落申请日。
  6. * 调用方:RefundOrderClass、CgRefundClass、统计类、售后 Controller/Service。
  7. */
  8. namespace bizGhs\order\classes;
  9. use biz\shop\classes\ShopClass;
  10. use bizGhs\custom\classes\CustomClass;
  11. use bizGhs\custom\services\GhsRechargeSettleService;
  12. use bizGhs\product\classes\ProductClass;
  13. use bizGhs\shop\classes\MainClass;
  14. use bizGhs\stock\classes\StockRecordClass;
  15. use bizHd\cg\classes\CgRefundClass;
  16. use bizHd\purchase\classes\PurchaseClass;
  17. use common\components\dict;
  18. use common\components\util;
  19. use Yii;
  20. class NextDayRefundClass
  21. {
  22. const SAME_DAY_YES = 1;
  23. const SAME_DAY_NO = 0;
  24. /** 三态开关:否 */
  25. const FLAG_NO = 0;
  26. /** 三态开关:是 */
  27. const FLAG_YES = 1;
  28. /**
  29. * 是否有有效支付时间(售后唯一时间依据;无支付时间不能售后)
  30. */
  31. public static function hasValidPayTime($order)
  32. {
  33. if (empty($order)) {
  34. return false;
  35. }
  36. $payTime = $order->payTime ?? '';
  37. return !empty($payTime) && $payTime !== '0000-00-00 00:00:00';
  38. }
  39. /**
  40. * 售后入口校验:无支付时间直接拦截
  41. */
  42. public static function assertCanRefund($order)
  43. {
  44. if (empty($order)) {
  45. util::fail('没有原订单');
  46. }
  47. if (!self::hasValidPayTime($order)) {
  48. util::fail('订单无支付时间,不能售后');
  49. }
  50. }
  51. /**
  52. * 原单是否可走跨天售后(即原「冲销」路径):
  53. * - 非当天支付(仅看 payTime);或
  54. * - 挂账已结清;或
  55. * - 已结账单(当天已结账也按冲销处理,不走普通售后)
  56. * @return array{ok:bool,reason:string,sameDay:int}
  57. */
  58. public static function checkEligible($order)
  59. {
  60. if (empty($order)) {
  61. return ['ok' => false, 'reason' => '没有原订单', 'sameDay' => self::SAME_DAY_YES, 'hasPayTime' => 0];
  62. }
  63. // 只用支付时间;无支付时间本单不能售后(由 assertCanRefund 拦截)
  64. if (!self::hasValidPayTime($order)) {
  65. return [
  66. 'ok' => false,
  67. 'reason' => '订单无支付时间,不能售后',
  68. 'sameDay' => self::SAME_DAY_YES,
  69. 'hasPayTime' => 0,
  70. ];
  71. }
  72. $payTime = $order->payTime;
  73. $isToday = date('Y-m-d', strtotime($payTime)) === date('Y-m-d');
  74. $debtPrice = bcadd((string)($order->debtPrice ?? '0'), '0', 2);
  75. $remainDebt = bcadd((string)($order->remainDebtPrice ?? '0'), '0', 2);
  76. $debtCleared = bccomp($debtPrice, '0', 2) > 0 && bccomp($remainDebt, '0', 2) === 0;
  77. // 已结账:当天也强制走冲销(sameDay=0),与隔天同一套资金/金额逻辑
  78. $orderCleared = !empty($order->clearId);
  79. if ($isToday && !$debtCleared && !$orderCleared) {
  80. return [
  81. 'ok' => false,
  82. 'reason' => '当天未结账订单请走普通售后;已结账/挂账结清/隔天可售后',
  83. 'sameDay' => self::SAME_DAY_YES,
  84. 'hasPayTime' => 1,
  85. ];
  86. }
  87. return [
  88. 'ok' => true,
  89. 'reason' => '',
  90. 'sameDay' => self::SAME_DAY_NO,
  91. 'orderCleared' => $orderCleared ? 1 : 0,
  92. 'hasPayTime' => 1,
  93. ];
  94. }
  95. /**
  96. * 创建/审核时是否必须按跨天售后(sameDay=0):
  97. * 与 checkEligible 对齐——非当天 / 已结账单 / 挂账已结清,均不可再走当天售后。
  98. * 无支付时间返回 false(须先走 assertCanRefund)。
  99. */
  100. public static function mustUseNextDay($order)
  101. {
  102. if (!self::hasValidPayTime($order)) {
  103. return false;
  104. }
  105. $check = self::checkEligible($order);
  106. return !empty($check['ok']);
  107. }
  108. /**
  109. * 是否微信/支付宝线上支付(看订单或售后快照)
  110. */
  111. public static function isOnlinePayOrder($orderOrRefund)
  112. {
  113. if (empty($orderOrRefund)) {
  114. return false;
  115. }
  116. $payWay = intval($orderOrRefund->payWay ?? 0);
  117. $onlinePay = intval($orderOrRefund->onlinePay ?? dict::getDict('onlinePay', 'not'));
  118. $wxPay = dict::getDict('payWay', 'wxPay');
  119. $aliPay = dict::getDict('payWay', 'alipay');
  120. return $onlinePay == dict::getDict('onlinePay', 'yes')
  121. && in_array($payWay, [$wxPay, $aliPay], true);
  122. }
  123. /**
  124. * 余额付或挂账:原路就是动余额/欠款,禁止再「返充」
  125. */
  126. public static function isBalanceOrDebtPayWay($payWay)
  127. {
  128. $payWay = intval($payWay);
  129. return $payWay === intval(dict::getDict('payWay', 'balancePay'))
  130. || $payWay === intval(dict::getDict('payWay', 'debtPay'));
  131. }
  132. /**
  133. * 是否需人工确认原路(现金/银行卡/线下微信支付宝等)
  134. * 线上通道成功、余额/挂账账务完成可自动 hasReturn;线下钱是否退还要人确认。
  135. */
  136. public static function needsManualReturnConfirm($orderOrRefund)
  137. {
  138. if (empty($orderOrRefund)) {
  139. return false;
  140. }
  141. if (self::isOnlinePayOrder($orderOrRefund)) {
  142. return false;
  143. }
  144. $payWay = intval($orderOrRefund->payWay ?? 0);
  145. if (self::isBalanceOrDebtPayWay($payWay)) {
  146. return false;
  147. }
  148. return true;
  149. }
  150. /**
  151. * 是否待选手选资金落地(成功页二选一)
  152. */
  153. public static function needFundAction($refund)
  154. {
  155. if (empty($refund)) {
  156. return 0;
  157. }
  158. if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) {
  159. return 0;
  160. }
  161. if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) {
  162. return 0;
  163. }
  164. if (intval($refund->couldReturn ?? 0) !== self::FLAG_YES) {
  165. return 0;
  166. }
  167. return 1;
  168. }
  169. /**
  170. * 生成售后资金快照(落 xhRefund/xhCgRefund)
  171. * - 有原单:onlinePay/payWay 与订单一致;余额/挂账 couldReturn=0,其它=1
  172. * - 无原单:线下渠道由选手选 payWay;couldReturn=1(成功页可改走余额)
  173. *
  174. * @param object|null $order 原销售单;无原单传 null
  175. * @param bool $hasRelateOrder 是否有关联原单
  176. * @param array $options payWay(无原单线下渠道)
  177. * @return array{onlinePay:int,payWay:int,hasReturn:int,couldReturn:int,returnBalance:int}
  178. */
  179. public static function buildRefundPaySnapshot($order = null, $hasRelateOrder = true, $options = [])
  180. {
  181. $onlineNot = intval(dict::getDict('onlinePay', 'not'));
  182. // 兼容旧调用名:内部仍可被 buildRefundFundSnapshot 转发
  183. if (!$hasRelateOrder || empty($order)) {
  184. $payWay = self::normalizeOfflinePayWay($options['payWay'] ?? null, null);
  185. return [
  186. 'onlinePay' => $onlineNot,
  187. 'payWay' => $payWay,
  188. 'hasReturn' => self::FLAG_NO,
  189. 'couldReturn' => self::FLAG_YES,
  190. 'returnBalance' => self::FLAG_NO,
  191. ];
  192. }
  193. $payWay = intval($order->payWay ?? 0);
  194. $onlinePay = intval($order->onlinePay ?? $onlineNot);
  195. $couldReturn = self::isBalanceOrDebtPayWay($payWay) ? self::FLAG_NO : self::FLAG_YES;
  196. return [
  197. 'onlinePay' => $onlinePay,
  198. 'payWay' => $payWay,
  199. 'hasReturn' => self::FLAG_NO,
  200. 'couldReturn' => $couldReturn,
  201. 'returnBalance' => self::FLAG_NO,
  202. ];
  203. }
  204. /**
  205. * @deprecated 已废弃 fundType;保留转发以免旧调用报错
  206. */
  207. public static function buildRefundFundSnapshot($fundTypeInput, $order = null, $hasRelateOrder = true, $options = [])
  208. {
  209. return self::buildRefundPaySnapshot($order, $hasRelateOrder, $options);
  210. }
  211. /**
  212. * 线下转账渠道:允许 0微信/1支付宝/4现金/5银行卡;非法则回落原单或现金
  213. */
  214. protected static function normalizeOfflinePayWay($payWay, $order = null)
  215. {
  216. $allowed = [
  217. intval(dict::getDict('payWay', 'wxPay')),
  218. intval(dict::getDict('payWay', 'alipay')),
  219. intval(dict::getDict('payWay', 'cash')),
  220. intval(dict::getDict('payWay', 'bankCard')),
  221. ];
  222. if ($payWay !== null && $payWay !== '' && in_array(intval($payWay), $allowed, true)) {
  223. return intval($payWay);
  224. }
  225. if (!empty($order)) {
  226. $orderPayWay = intval($order->payWay ?? -1);
  227. if (in_array($orderPayWay, $allowed, true)) {
  228. return $orderPayWay;
  229. }
  230. }
  231. return intval(dict::getDict('payWay', 'cash'));
  232. }
  233. /**
  234. * 售后单与采购镜像同步资金三态
  235. */
  236. public static function syncRefundFundFlags($refund, $fields = [])
  237. {
  238. if (empty($refund) || empty($fields)) {
  239. return;
  240. }
  241. foreach ($fields as $k => $v) {
  242. $refund->$k = $v;
  243. }
  244. $refund->save(false, array_keys($fields));
  245. $cgRefundId = intval($refund->cgRefundId ?? 0);
  246. if ($cgRefundId <= 0) {
  247. return;
  248. }
  249. $cgRefund = CgRefundClass::getById($cgRefundId, true);
  250. if (empty($cgRefund)) {
  251. return;
  252. }
  253. foreach ($fields as $k => $v) {
  254. $cgRefund->$k = $v;
  255. }
  256. $cgRefund->save(false, array_keys($fields));
  257. }
  258. /**
  259. * 标记已原路退回(现金/银行卡等线下人工确认)
  260. */
  261. public static function markHasReturn($refund)
  262. {
  263. if (empty($refund)) {
  264. util::fail('退款单不存在');
  265. }
  266. if (intval($refund->status) !== RefundOrderClass::STATUS_COMPLETE) {
  267. util::fail('售后未通过,不能确认原路退回');
  268. }
  269. if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) {
  270. util::fail('已返充余额,不能再标记原路退回');
  271. }
  272. if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) {
  273. return $refund;
  274. }
  275. self::syncRefundFundFlags($refund, ['hasReturn' => self::FLAG_YES]);
  276. return RefundOrderClass::getById($refund->id, true);
  277. }
  278. /**
  279. * 不想线下原路时:返充到客户余额(须 couldReturn=1 且尚未原路)
  280. * @return array{refund:object,customBalance:string}
  281. */
  282. public static function applyReturnBalance($refund, $order = null)
  283. {
  284. if (empty($refund)) {
  285. util::fail('退款单不存在');
  286. }
  287. if (intval($refund->status) !== RefundOrderClass::STATUS_COMPLETE) {
  288. util::fail('售后未通过,不能返充余额');
  289. }
  290. if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) {
  291. util::fail('已原路退回,不能再返充余额');
  292. }
  293. if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) {
  294. util::fail('已返充到余额');
  295. }
  296. if (intval($refund->couldReturn ?? 0) !== self::FLAG_YES) {
  297. util::fail('该单不允许返充到余额');
  298. }
  299. $customId = intval($refund->customId ?? 0);
  300. $custom = CustomClass::getLockById($customId);
  301. if (empty($custom)) {
  302. util::fail('没有找到客户');
  303. }
  304. $pair = GhsRechargeSettleService::lockAccountPair($custom);
  305. CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, $order);
  306. self::syncRefundFundFlags($refund, ['returnBalance' => self::FLAG_YES]);
  307. $custom = CustomClass::getById($customId, true);
  308. return [
  309. 'refund' => RefundOrderClass::getById($refund->id, true),
  310. 'customBalance' => bcadd((string)($custom->balance ?? '0'), '0', 2),
  311. ];
  312. }
  313. /**
  314. * GHS 侧隔天通过:累计 nextDayTkPrice(不改 actPrice);扣 buyAmount;
  315. * 余额/挂账原路加回客户余额;线上付扣门店可提现余额(与当天售后一致,必须在 ghs 端做)。
  316. * @return string 当前客户余额
  317. */
  318. public static function applyGhsAmountAndFund($refund, $order)
  319. {
  320. $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
  321. $order->nextDayTkPrice = bcadd((string)($order->nextDayTkPrice ?? '0'), $refundPrice, 2);
  322. $order->refund = OrderClass::REFUND_YES;
  323. $order->save(false, ['nextDayTkPrice', 'refund']);
  324. $customId = intval($order->customId ?? 0);
  325. $custom = CustomClass::getLockById($customId);
  326. if (empty($custom)) {
  327. util::fail('没有找到客户');
  328. }
  329. // 累计消费与是否当天无关:退了就要从 buyAmount 扣掉
  330. $buyAmount = bcsub((string)($custom->buyAmount ?? '0'), $refundPrice, 2);
  331. if (bccomp($buyAmount, '0', 2) < 0) {
  332. $buyAmount = '0.00';
  333. }
  334. $custom->buyAmount = $buyAmount;
  335. $custom->save(false, ['buyAmount']);
  336. $payWay = intval($refund->payWay ?? ($order->payWay ?? 0));
  337. $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
  338. // 余额/挂账:原路=加回余额,完成后 hasReturn=1(couldReturn 已为 0)
  339. if (self::isBalanceOrDebtPayWay($payWay)) {
  340. $pair = GhsRechargeSettleService::lockAccountPair($custom);
  341. CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, $order);
  342. self::syncRefundFundFlags($refund, ['hasReturn' => self::FLAG_YES]);
  343. $custom = CustomClass::getById($customId, true);
  344. $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
  345. } elseif (self::isOnlinePayOrder($refund) || self::isOnlinePayOrder($order)) {
  346. // 线上付收款时加过门店可提现余额,跨天售后也要在 ghs 端扣回(拉卡拉原路仍在采购侧发起)
  347. $shopId = intval($order->shopId ?? ($refund->shopId ?? 0));
  348. $mainId = intval($order->mainId ?? ($refund->mainId ?? 0));
  349. $shop = ShopClass::getLockById($shopId);
  350. if (empty($shop)) {
  351. util::fail('没有找到门店');
  352. }
  353. $main = MainClass::getLockById($mainId);
  354. if (empty($main)) {
  355. util::fail('没有main信息');
  356. }
  357. ShopClass::saleRefundReduceBalance($main, $shop, $refund, $refundPrice);
  358. }
  359. // 现金/银行卡等线下原路等人工 markHasReturn
  360. return $balance;
  361. }
  362. /**
  363. * HD 采购侧隔天通过:累计 nextDayTkPrice;线上原路走拉卡拉成功后 hasReturn=1。
  364. */
  365. public static function applyCgAmountAndFund($cgRefund, $cg)
  366. {
  367. $refundPrice = bcadd((string)($cgRefund->refundPrice ?? '0'), '0', 2);
  368. $cg->nextDayTkPrice = bcadd((string)($cg->nextDayTkPrice ?? '0'), $refundPrice, 2);
  369. $cg->refund = PurchaseClass::REFUND_YES;
  370. $cg->save(false, ['nextDayTkPrice', 'refund']);
  371. if (self::isOnlinePayOrder($cgRefund) || self::isOnlinePayOrder($cg)) {
  372. CgRefundClass::nextDayOriginalOnlineRefund($cgRefund, $cg);
  373. $cgRefund->hasReturn = self::FLAG_YES;
  374. $cgRefund->save(false, ['hasReturn']);
  375. $saleRefundId = intval($cgRefund->saleRefundId ?? 0);
  376. if ($saleRefundId > 0) {
  377. $saleRefund = RefundOrderClass::getById($saleRefundId, true);
  378. if (!empty($saleRefund)) {
  379. self::syncRefundFundFlags($saleRefund, ['hasReturn' => self::FLAG_YES]);
  380. }
  381. }
  382. }
  383. $ghsId = intval($cg->ghsId ?? 0);
  384. if ($ghsId > 0) {
  385. $ghs = \bizHd\ghs\classes\GhsClass::getLockById($ghsId);
  386. if (!empty($ghs) && isset($ghs->expendAmount)) {
  387. $expend = bcsub((string)($ghs->expendAmount ?? '0'), $refundPrice, 2);
  388. if (bccomp($expend, '0', 2) < 0) {
  389. $expend = '0.00';
  390. }
  391. $ghs->expendAmount = $expend;
  392. $ghs->save(false, ['expendAmount']);
  393. }
  394. }
  395. }
  396. /**
  397. * 无原单退款通过:加库存(退货退款)、累计门店支出、扣客户消费;资金留给成功页确认原路/返充。
  398. * 不写订单 nextDayTkPrice(无关联销售单)。
  399. */
  400. public static function passFreeRefund($refund)
  401. {
  402. if (empty($refund)) {
  403. util::fail('退款单不存在');
  404. }
  405. if (intval($refund->status) === RefundOrderClass::STATUS_COMPLETE) {
  406. util::fail('已通过');
  407. }
  408. if (intval($refund->status) === RefundOrderClass::STATUS_REJECT) {
  409. util::fail('已驳回');
  410. }
  411. if (intval($refund->status) === RefundOrderClass::STATUS_CANCEL) {
  412. util::fail('已取消');
  413. }
  414. $refund->status = RefundOrderClass::STATUS_COMPLETE;
  415. $refund->passTime = date('Y-m-d H:i:s');
  416. $refund->save();
  417. $refundSn = $refund->orderSn ?? '';
  418. $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
  419. $refundType = intval($refund->refundType ?? RefundOrderClass::REFUND_TYPE_MONEY_GOOD);
  420. $shopId = intval($refund->shopId ?? 0);
  421. $mainId = intval($refund->mainId ?? 0);
  422. $sjId = intval($refund->sjId ?? 0);
  423. $customName = $refund->customName ?? '';
  424. // 有明细则置通过;仅「退货退款」回库(库存不退只记账不改库存)
  425. $refundItemList = RefundOrderItemClass::getAllByCondition(['orderSn' => $refundSn], null, '*', null, true);
  426. if ($refundType === RefundOrderClass::REFUND_TYPE_MONEY_GOOD && empty($refundItemList)) {
  427. util::fail('没有找到退款花材');
  428. }
  429. if (!empty($refundItemList)) {
  430. foreach ($refundItemList as $val) {
  431. $val->status = 1;
  432. $val->save();
  433. if ($refundType !== RefundOrderClass::REFUND_TYPE_MONEY_GOOD) {
  434. continue;
  435. }
  436. $productId = intval($val->productId ?? 0);
  437. $ptItemId = intval($val->itemId ?? 0);
  438. $num = bcadd((string)($val->xhNum ?? '0'), '0', 2);
  439. if (bccomp($num, '0', 2) <= 0 || $productId <= 0) {
  440. continue;
  441. }
  442. if (intval($val->xhUnitType ?? 0) === 1) {
  443. $smallNum = $num;
  444. $bigNum = 0;
  445. } else {
  446. $smallNum = 0;
  447. $bigNum = $num;
  448. }
  449. $stockInfo = ProductClass::addStock($productId, $bigNum, $smallNum);
  450. ProductClass::tryRecoverLimitBuyWhenStockRollbackFromZero($productId, $stockInfo['oldStock'] ?? 0);
  451. $oldStock = $stockInfo['oldStock'] ?? 0;
  452. $newStock = $stockInfo['newStock'] ?? 0;
  453. $recordData = [
  454. 'relateName' => $customName,
  455. 'itemNum' => $val->itemNum ?? 0,
  456. 'sjId' => $sjId,
  457. 'shopId' => $shopId,
  458. 'mainId' => $mainId,
  459. 'orderSn' => $refundSn,
  460. 'itemId' => $ptItemId,
  461. 'oldStock' => $oldStock,
  462. 'productId' => $productId,
  463. 'newStock' => $newStock,
  464. 'io' => 1,
  465. ];
  466. StockRecordClass::ghsRefundAddRecord($recordData);
  467. $val->itemStock = $oldStock;
  468. $val->newStock = $newStock;
  469. $val->save();
  470. }
  471. }
  472. $main = MainClass::getLockById($mainId);
  473. if (empty($main)) {
  474. util::fail('没有main信息');
  475. }
  476. $main->totalExpend = bcadd((string)($main->totalExpend ?? '0'), $refundPrice, 2);
  477. $main->totalRefund = bcadd((string)($main->totalRefund ?? '0'), $refundPrice, 2);
  478. $main->save();
  479. return self::applyFreeFund($refund);
  480. }
  481. /**
  482. * 无原单退款资金:仅扣减客户累计消费;原路/返充在成功页由 markHasReturn / applyReturnBalance 落地。
  483. * @return string 当前客户余额
  484. */
  485. public static function applyFreeFund($refund)
  486. {
  487. $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
  488. $customId = intval($refund->customId ?? 0);
  489. $custom = CustomClass::getLockById($customId);
  490. if (empty($custom)) {
  491. util::fail('没有找到客户');
  492. }
  493. $buyAmount = bcsub((string)($custom->buyAmount ?? '0'), $refundPrice, 2);
  494. if (bccomp($buyAmount, '0', 2) < 0) {
  495. $buyAmount = '0.00';
  496. }
  497. $custom->buyAmount = $buyAmount;
  498. $custom->save(false, ['buyAmount']);
  499. return bcadd((string)($custom->balance ?? '0'), '0', 2);
  500. }
  501. /**
  502. * 按时段汇总成功跨天售后金额(收入/销量统计扣减用)
  503. */
  504. public static function sumAmountByMainAndTime($mainId, $startTime, $endTime)
  505. {
  506. $sql = "SELECT COALESCE(SUM(refundPrice),0) AS total FROM xhRefund
  507. WHERE mainId=:mainId AND status=:status AND sameDay=:sameDay
  508. AND passTime BETWEEN :start AND :end";
  509. $row = Yii::$app->db->createCommand($sql, [
  510. ':mainId' => $mainId,
  511. ':status' => RefundOrderClass::STATUS_COMPLETE,
  512. ':sameDay' => self::SAME_DAY_NO,
  513. ':start' => $startTime,
  514. ':end' => $endTime,
  515. ])->queryOne();
  516. return bcadd($row['total'] ?? '0', '0', 2);
  517. }
  518. /**
  519. * 渠道收入对冲:拉取隔天成功售后及原单支付字段
  520. */
  521. public static function listForChannelIncomeDeduct($mainId, $startTime, $endTime)
  522. {
  523. $sql = "SELECT r.refundPrice AS amount, r.hasReturn, r.returnBalance, r.couldReturn,
  524. r.onlinePay AS refundOnlinePay, r.payWay AS refundPayWay,
  525. o.id AS orderId, o.debtPrice, o.remainDebtPrice, o.onlinePay, o.payWay AS orderPayWay, o.payWay
  526. FROM xhRefund r
  527. LEFT JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
  528. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  529. AND r.passTime BETWEEN :start AND :end";
  530. $rows = Yii::$app->db->createCommand($sql, [
  531. ':mainId' => $mainId,
  532. ':status' => RefundOrderClass::STATUS_COMPLETE,
  533. ':sameDay' => self::SAME_DAY_NO,
  534. ':start' => $startTime,
  535. ':end' => $endTime,
  536. ])->queryAll();
  537. return $rows ?: [];
  538. }
  539. /**
  540. * 按客户汇总隔天退款金额
  541. */
  542. public static function sumAmountGroupByCustom($mainId, $startTime, $endTime)
  543. {
  544. $sql = "SELECT r.customId, COALESCE(SUM(r.refundPrice),0) AS total FROM xhRefund r
  545. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  546. AND r.passTime BETWEEN :start AND :end
  547. GROUP BY r.customId";
  548. $rows = Yii::$app->db->createCommand($sql, [
  549. ':mainId' => $mainId,
  550. ':status' => RefundOrderClass::STATUS_COMPLETE,
  551. ':sameDay' => self::SAME_DAY_NO,
  552. ':start' => $startTime,
  553. ':end' => $endTime,
  554. ])->queryAll();
  555. $map = [];
  556. foreach ($rows as $row) {
  557. $map[$row['customId']] = bcadd($row['total'] ?? '0', '0', 2);
  558. }
  559. return $map;
  560. }
  561. /**
  562. * 按客户汇总隔天退货数量(仅退货退款;通过日无开单时客户业绩扎数可显示为负)
  563. * @return array customId => num
  564. */
  565. public static function sumItemQtyByCustom($mainId, $startTime, $endTime)
  566. {
  567. $sql = "SELECT r.customId, COALESCE(SUM(i.xhNum),0) AS num
  568. FROM xhRefundItem i
  569. INNER JOIN xhRefund r ON r.orderSn = i.orderSn
  570. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  571. AND r.refundType=:rtype
  572. AND r.passTime BETWEEN :start AND :end
  573. GROUP BY r.customId";
  574. $rows = Yii::$app->db->createCommand($sql, [
  575. ':mainId' => $mainId,
  576. ':status' => RefundOrderClass::STATUS_COMPLETE,
  577. ':sameDay' => self::SAME_DAY_NO,
  578. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
  579. ':start' => $startTime,
  580. ':end' => $endTime,
  581. ])->queryAll();
  582. $map = [];
  583. foreach ($rows as $row) {
  584. $map[$row['customId']] = bcadd($row['num'] ?? '0', '0', 2);
  585. }
  586. return $map;
  587. }
  588. /**
  589. * 按花材汇总隔天退货数量与金额(退货退款明细)
  590. */
  591. public static function sumItemByProduct($mainId, $startTime, $endTime)
  592. {
  593. $sql = "SELECT i.productId, COALESCE(SUM(i.xhNum),0) AS num, COALESCE(SUM(i.xhPrice),0) AS amount
  594. FROM xhRefundItem i
  595. INNER JOIN xhRefund r ON r.orderSn = i.orderSn
  596. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  597. AND r.refundType=:rtype
  598. AND r.passTime BETWEEN :start AND :end
  599. GROUP BY i.productId";
  600. $rows = Yii::$app->db->createCommand($sql, [
  601. ':mainId' => $mainId,
  602. ':status' => RefundOrderClass::STATUS_COMPLETE,
  603. ':sameDay' => self::SAME_DAY_NO,
  604. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
  605. ':start' => $startTime,
  606. ':end' => $endTime,
  607. ])->queryAll();
  608. $map = [];
  609. foreach ($rows as $row) {
  610. $map[$row['productId']] = [
  611. 'num' => bcadd($row['num'] ?? '0', '0', 2),
  612. 'amount' => bcadd($row['amount'] ?? '0', '0', 2),
  613. ];
  614. }
  615. return $map;
  616. }
  617. /**
  618. * 支付日净销量:当天已退 = refundNum - nextRefundNum,净销 = xhNum - 当天已退。
  619. * 隔天退货记在 nextRefundNum,不在支付日从销量扣掉(通过日再扣)。
  620. *
  621. * @param mixed $xhNum 下单数量
  622. * @param mixed $refundNum 总已退
  623. * @param mixed $nextRefundNum 隔天已退
  624. * @return string
  625. */
  626. public static function payDayRemainNum($xhNum, $refundNum, $nextRefundNum = 0)
  627. {
  628. $sameDayRefund = bcsub((string)$refundNum, (string)($nextRefundNum ?? 0), 2);
  629. if (bccomp($sameDayRefund, '0', 2) < 0) {
  630. $sameDayRefund = '0';
  631. }
  632. return bcsub((string)$xhNum, $sameDayRefund, 2);
  633. }
  634. /**
  635. * @deprecated 已改用订单行 nextRefundNum + payDayRemainNum,保留兼容旧调用
  636. * 关联原单上「已发生」的隔天退货(不限通过日):用于销量按 payTime 统计时加回 refundNum 中的隔天部分,
  637. * 避免与按 passTime 扣减重复;口径对齐客户业绩(支付日先全额,通过日再扣)。
  638. *
  639. * @param int $mainId
  640. * @param string[] $relateOrderSns 原销售单号列表
  641. * @return array productId => [num, amount]
  642. */
  643. public static function sumItemByProductForRelateOrderSns($mainId, $relateOrderSns)
  644. {
  645. $relateOrderSns = array_values(array_unique(array_filter($relateOrderSns)));
  646. if (empty($relateOrderSns)) {
  647. return [];
  648. }
  649. $snParams = [];
  650. foreach ($relateOrderSns as $i => $sn) {
  651. $snParams[':sn' . $i] = $sn;
  652. }
  653. $in = implode(',', array_keys($snParams));
  654. $sql = "SELECT i.productId, COALESCE(SUM(i.xhNum),0) AS num, COALESCE(SUM(i.xhPrice),0) AS amount
  655. FROM xhRefundItem i
  656. INNER JOIN xhRefund r ON r.orderSn = i.orderSn
  657. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  658. AND r.refundType=:rtype
  659. AND r.relateOrderSn IN ($in)
  660. GROUP BY i.productId";
  661. $params = array_merge([
  662. ':mainId' => $mainId,
  663. ':status' => RefundOrderClass::STATUS_COMPLETE,
  664. ':sameDay' => self::SAME_DAY_NO,
  665. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
  666. ], $snParams);
  667. $rows = Yii::$app->db->createCommand($sql, $params)->queryAll();
  668. $map = [];
  669. foreach ($rows as $row) {
  670. $map[$row['productId']] = [
  671. 'num' => bcadd($row['num'] ?? '0', '0', 2),
  672. 'amount' => bcadd($row['amount'] ?? '0', '0', 2),
  673. ];
  674. }
  675. return $map;
  676. }
  677. /**
  678. * 隔天「仅退款」按通过日汇总金额(无花材明细,进收支分类「销售仅退款」等)
  679. */
  680. public static function sumMoneyOnlyAmountByMainAndTime($mainId, $startTime, $endTime)
  681. {
  682. $sql = "SELECT COALESCE(SUM(refundPrice),0) AS total FROM xhRefund
  683. WHERE mainId=:mainId AND status=:status AND sameDay=:sameDay
  684. AND refundType=:rtype
  685. AND passTime BETWEEN :start AND :end";
  686. $row = Yii::$app->db->createCommand($sql, [
  687. ':mainId' => $mainId,
  688. ':status' => RefundOrderClass::STATUS_COMPLETE,
  689. ':sameDay' => self::SAME_DAY_NO,
  690. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY,
  691. ':start' => $startTime,
  692. ':end' => $endTime,
  693. ])->queryOne();
  694. return bcadd($row['total'] ?? '0', '0', 2);
  695. }
  696. /**
  697. * 隔天「仅退款」按通过日分摊到原单花材(无明细时按原单行金额占比分摊 refundPrice)
  698. * 花材销量页用:通过日扣金额,数量不扣。
  699. *
  700. * @return array productId => [num=>0, amount=>...]
  701. */
  702. public static function sumMoneyOnlyAmountByProduct($mainId, $startTime, $endTime)
  703. {
  704. $sql = "SELECT r.id, r.refundPrice, r.relateOrderSn
  705. FROM xhRefund r
  706. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  707. AND r.refundType=:rtype
  708. AND IFNULL(NULLIF(r.relateOrderSn,''), '') <> ''
  709. AND r.passTime BETWEEN :start AND :end";
  710. $rows = Yii::$app->db->createCommand($sql, [
  711. ':mainId' => $mainId,
  712. ':status' => RefundOrderClass::STATUS_COMPLETE,
  713. ':sameDay' => self::SAME_DAY_NO,
  714. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY,
  715. ':start' => $startTime,
  716. ':end' => $endTime,
  717. ])->queryAll();
  718. if (empty($rows)) {
  719. return [];
  720. }
  721. $map = [];
  722. foreach ($rows as $row) {
  723. $refundPrice = bcadd((string)($row['refundPrice'] ?? '0'), '0', 2);
  724. if (bccomp($refundPrice, '0', 2) <= 0) {
  725. continue;
  726. }
  727. $relateSn = $row['relateOrderSn'] ?? '';
  728. $items = \bizGhs\order\classes\OrderItemClass::getAllByCondition(
  729. ['orderSn' => $relateSn],
  730. null,
  731. 'productId,xhNum,xhUnitPrice',
  732. null,
  733. true
  734. );
  735. if (empty($items)) {
  736. continue;
  737. }
  738. $weights = [];
  739. $weightSum = '0.00';
  740. foreach ($items as $item) {
  741. $pid = intval($item->productId ?? 0);
  742. if ($pid <= 0) {
  743. continue;
  744. }
  745. $line = bcmul((string)($item->xhNum ?? '0'), (string)($item->xhUnitPrice ?? '0'), 2);
  746. if (bccomp($line, '0', 2) <= 0) {
  747. continue;
  748. }
  749. $weights[$pid] = bcadd($weights[$pid] ?? '0', $line, 2);
  750. $weightSum = bcadd($weightSum, $line, 2);
  751. }
  752. if (bccomp($weightSum, '0', 2) <= 0) {
  753. continue;
  754. }
  755. $allocated = '0.00';
  756. $pids = array_keys($weights);
  757. $last = count($pids) - 1;
  758. foreach ($pids as $idx => $pid) {
  759. if ($idx === $last) {
  760. $part = bcsub($refundPrice, $allocated, 2);
  761. } else {
  762. $part = bcdiv(bcmul($refundPrice, $weights[$pid], 4), $weightSum, 2);
  763. $allocated = bcadd($allocated, $part, 2);
  764. }
  765. if (!isset($map[$pid])) {
  766. $map[$pid] = ['num' => '0.00', 'amount' => '0.00'];
  767. }
  768. $map[$pid]['amount'] = bcadd($map[$pid]['amount'], $part, 2);
  769. }
  770. }
  771. return $map;
  772. }
  773. /**
  774. * 按配送员汇总隔天退货数量/金额(通过日;原单 sendStaffId)
  775. * @return array sendStaffId => [num, amount]
  776. */
  777. public static function sumItemBySendStaff($mainId, $startTime, $endTime)
  778. {
  779. $sql = "SELECT IFNULL(o.sendStaffId,0) AS sendStaffId,
  780. COALESCE(SUM(i.xhNum),0) AS num, COALESCE(SUM(i.xhPrice),0) AS amount
  781. FROM xhRefundItem i
  782. INNER JOIN xhRefund r ON r.orderSn = i.orderSn
  783. INNER JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
  784. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  785. AND r.refundType=:rtype
  786. AND r.passTime BETWEEN :start AND :end
  787. GROUP BY IFNULL(o.sendStaffId,0)";
  788. $rows = Yii::$app->db->createCommand($sql, [
  789. ':mainId' => $mainId,
  790. ':status' => RefundOrderClass::STATUS_COMPLETE,
  791. ':sameDay' => self::SAME_DAY_NO,
  792. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
  793. ':start' => $startTime,
  794. ':end' => $endTime,
  795. ])->queryAll();
  796. return self::mapStaffNumAmount($rows);
  797. }
  798. /**
  799. * @deprecated 已改用订单行 nextRefundNum + payDayRemainNum,保留兼容旧调用
  800. * 关联原单上已发生的隔天退货,按配送员汇总(支付日统计加回用)
  801. */
  802. public static function sumItemBySendStaffForRelateOrderSns($mainId, $relateOrderSns)
  803. {
  804. $relateOrderSns = array_values(array_unique(array_filter($relateOrderSns)));
  805. if (empty($relateOrderSns)) {
  806. return [];
  807. }
  808. $snParams = [];
  809. foreach ($relateOrderSns as $i => $sn) {
  810. $snParams[':sn' . $i] = $sn;
  811. }
  812. $in = implode(',', array_keys($snParams));
  813. $sql = "SELECT IFNULL(o.sendStaffId,0) AS sendStaffId,
  814. COALESCE(SUM(i.xhNum),0) AS num, COALESCE(SUM(i.xhPrice),0) AS amount
  815. FROM xhRefundItem i
  816. INNER JOIN xhRefund r ON r.orderSn = i.orderSn
  817. INNER JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
  818. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  819. AND r.refundType=:rtype
  820. AND r.relateOrderSn IN ($in)
  821. GROUP BY IFNULL(o.sendStaffId,0)";
  822. $params = array_merge([
  823. ':mainId' => $mainId,
  824. ':status' => RefundOrderClass::STATUS_COMPLETE,
  825. ':sameDay' => self::SAME_DAY_NO,
  826. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
  827. ], $snParams);
  828. $rows = Yii::$app->db->createCommand($sql, $params)->queryAll();
  829. return self::mapStaffNumAmount($rows);
  830. }
  831. /**
  832. * 隔天仅退款按配送员汇总金额(通过日)
  833. */
  834. public static function sumMoneyOnlyAmountBySendStaff($mainId, $startTime, $endTime)
  835. {
  836. $sql = "SELECT IFNULL(o.sendStaffId,0) AS sendStaffId, COALESCE(SUM(r.refundPrice),0) AS amount
  837. FROM xhRefund r
  838. INNER JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
  839. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  840. AND r.refundType=:rtype
  841. AND IFNULL(NULLIF(r.relateOrderSn,''), '') <> ''
  842. AND r.passTime BETWEEN :start AND :end
  843. GROUP BY IFNULL(o.sendStaffId,0)";
  844. $rows = Yii::$app->db->createCommand($sql, [
  845. ':mainId' => $mainId,
  846. ':status' => RefundOrderClass::STATUS_COMPLETE,
  847. ':sameDay' => self::SAME_DAY_NO,
  848. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY,
  849. ':start' => $startTime,
  850. ':end' => $endTime,
  851. ])->queryAll();
  852. $map = [];
  853. foreach ($rows as $row) {
  854. $sid = intval($row['sendStaffId'] ?? 0);
  855. $map[$sid] = [
  856. 'num' => '0.00',
  857. 'amount' => bcadd($row['amount'] ?? '0', '0', 2),
  858. ];
  859. }
  860. return $map;
  861. }
  862. /**
  863. * 跨天售后对原单账单日的影响:筛售后创建时间,按 xhGhsOrder.payTime 汇总日/月金额。
  864. * 只 INNER JOIN 批发单,零售/采购不算;只统计已通过,避免未审核当成已冲回。
  865. *
  866. * @param int $mainId
  867. * @param string $startTime 售后 addTime 起(含时分秒)
  868. * @param string $endTime 售后 addTime 止
  869. * @return array{dayList:array,monthList:array,totalAmount:float}
  870. */
  871. public static function affectByOrderPayTime($mainId, $startTime, $endTime)
  872. {
  873. $sql = "SELECT DATE(o.payTime) AS billDay,
  874. DATE_FORMAT(o.payTime, '%Y-%m') AS billMonth,
  875. COALESCE(SUM(r.refundPrice), 0) AS amount
  876. FROM xhRefund r
  877. INNER JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn AND o.mainId = r.mainId
  878. WHERE r.mainId = :mainId
  879. AND r.status = :status
  880. AND r.sameDay = :sameDay
  881. AND r.addTime BETWEEN :start AND :end
  882. AND o.payTime > '1970-01-01'
  883. GROUP BY billDay, billMonth
  884. ORDER BY billDay DESC";
  885. $rows = Yii::$app->db->createCommand($sql, [
  886. ':mainId' => $mainId,
  887. ':status' => RefundOrderClass::STATUS_COMPLETE,
  888. ':sameDay' => self::SAME_DAY_NO,
  889. ':start' => $startTime,
  890. ':end' => $endTime,
  891. ])->queryAll();
  892. $dayList = [];
  893. $monthMap = [];
  894. $totalAmount = '0.00';
  895. foreach ($rows ?: [] as $row) {
  896. $amount = bcadd((string)($row['amount'] ?? '0'), '0', 2);
  897. if (bccomp($amount, '0', 2) == 0) {
  898. continue;
  899. }
  900. $dayList[] = [
  901. 'day' => $row['billDay'] ?? '',
  902. 'amount' => floatval($amount),
  903. ];
  904. $month = $row['billMonth'] ?? '';
  905. if ($month !== '') {
  906. if (!isset($monthMap[$month])) {
  907. $monthMap[$month] = '0.00';
  908. }
  909. $monthMap[$month] = bcadd($monthMap[$month], $amount, 2);
  910. }
  911. $totalAmount = bcadd($totalAmount, $amount, 2);
  912. }
  913. krsort($monthMap);
  914. $monthList = [];
  915. foreach ($monthMap as $month => $amt) {
  916. $monthList[] = [
  917. 'month' => $month,
  918. 'amount' => floatval($amt),
  919. ];
  920. }
  921. return [
  922. 'dayList' => $dayList,
  923. 'monthList' => $monthList,
  924. 'totalAmount' => floatval($totalAmount),
  925. ];
  926. }
  927. /**
  928. * 与 affectByOrderPayTime 同口径的售后明细:售后 addTime + 原单 payTime,必须挂上批发单。
  929. * /refund/list?matchPayTime=1 用,避免列表按 xhRefund.orderTime 或漏申请日导致金额对不上。
  930. *
  931. * @param int $mainId
  932. * @param array $refundPeriod addTime 起止(含时分秒)
  933. * @param array $payPeriod 原单 payTime 起止
  934. * @param array $extra status/sameDay/customId/shopAdminId,-1 或不传表示不限
  935. * @return array{list:array,totalNum:int,totalPage:int,moreData:int,totalRefundPrice:string}
  936. */
  937. public static function getListByOrderPayTime($mainId, $refundPeriod, $payPeriod, $extra = [])
  938. {
  939. $addStart = $refundPeriod['startTime'] ?? '';
  940. $addEnd = $refundPeriod['endTime'] ?? '';
  941. $payStart = $payPeriod['startTime'] ?? '';
  942. $payEnd = $payPeriod['endTime'] ?? '';
  943. $params = [
  944. ':mainId' => intval($mainId),
  945. ':addStart' => $addStart,
  946. ':addEnd' => $addEnd,
  947. ':payStart' => $payStart,
  948. ':payEnd' => $payEnd,
  949. ];
  950. $whereSql = "r.mainId = :mainId
  951. AND r.addTime BETWEEN :addStart AND :addEnd
  952. AND o.payTime BETWEEN :payStart AND :payEnd
  953. AND o.payTime > '1970-01-01'";
  954. // 状态/跨天与影响页一致时才收窄,列表改 tab 后按用户当前筛选项
  955. $status = isset($extra['status']) ? intval($extra['status']) : -1;
  956. if ($status > -1) {
  957. $whereSql .= ' AND r.status = :status';
  958. $params[':status'] = $status;
  959. }
  960. $sameDay = isset($extra['sameDay']) ? intval($extra['sameDay']) : -1;
  961. if ($sameDay === 0 || $sameDay === 1) {
  962. $whereSql .= ' AND r.sameDay = :sameDay';
  963. $params[':sameDay'] = $sameDay;
  964. }
  965. $customId = intval($extra['customId'] ?? 0);
  966. if ($customId > 0) {
  967. $whereSql .= ' AND r.customId = :customId';
  968. $params[':customId'] = $customId;
  969. }
  970. $shopAdminId = intval($extra['shopAdminId'] ?? 0);
  971. if ($shopAdminId > 0) {
  972. $whereSql .= ' AND r.shopAdminId = :shopAdminId';
  973. $params[':shopAdminId'] = $shopAdminId;
  974. }
  975. // 从花材销量/列表带通过日时,只认真实 passTime
  976. $passStart = $extra['passStart'] ?? '';
  977. $passEnd = $extra['passEnd'] ?? '';
  978. if ($passStart !== '' && $passEnd !== '') {
  979. $whereSql .= " AND r.passTime BETWEEN :passStart AND :passEnd AND r.passTime > '1970-01-01'";
  980. $params[':passStart'] = $passStart;
  981. $params[':passEnd'] = $passEnd;
  982. }
  983. $fromSql = "FROM xhRefund r
  984. INNER JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn AND o.mainId = r.mainId
  985. WHERE {$whereSql}";
  986. $count = intval(Yii::$app->db->createCommand("SELECT COUNT(*) {$fromSql}", $params)->queryScalar());
  987. $totalRefundPrice = bcadd(
  988. (string)(Yii::$app->db->createCommand("SELECT COALESCE(SUM(r.refundPrice),0) {$fromSql}", $params)->queryScalar() ?: '0'),
  989. '0',
  990. 2
  991. );
  992. $get = Yii::$app->request->get();
  993. $page = max(1, intval($get['page'] ?? 1));
  994. $pageSize = isset($get['pageSize']) && !empty($get['pageSize'])
  995. ? max(1, intval($get['pageSize']))
  996. : intval(Yii::$app->params['pageSize'] ?? 20);
  997. $offset = ($page - 1) * $pageSize;
  998. $totalPage = $pageSize > 0 ? (int)ceil($count / $pageSize) : 1;
  999. $list = Yii::$app->db->createCommand(
  1000. "SELECT r.* {$fromSql} ORDER BY r.addTime DESC LIMIT {$offset}, {$pageSize}",
  1001. $params
  1002. )->queryAll();
  1003. return [
  1004. 'list' => $list ?: [],
  1005. 'totalNum' => $count,
  1006. 'totalPage' => $totalPage,
  1007. 'moreData' => $totalPage > $page ? 1 : 0,
  1008. 'totalRefundPrice' => $totalRefundPrice,
  1009. ];
  1010. }
  1011. /**
  1012. * @param array $rows
  1013. * @return array
  1014. */
  1015. protected static function mapStaffNumAmount($rows)
  1016. {
  1017. $map = [];
  1018. foreach ($rows as $row) {
  1019. $sid = intval($row['sendStaffId'] ?? 0);
  1020. $map[$sid] = [
  1021. 'num' => bcadd($row['num'] ?? '0', '0', 2),
  1022. 'amount' => bcadd($row['amount'] ?? '0', '0', 2),
  1023. ];
  1024. }
  1025. return $map;
  1026. }
  1027. }