NextDayRefundService.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. <?php
  2. /**
  3. * 售后资金/隔天售后辅助服务
  4. * 用途:资格校验、资金快照(fundType/onlinePay/payWay)、隔天通过后的金额与返余额。
  5. * 字段:sameDay 区分当天/隔天;fundType 当天隔天通用;onlinePay/payWay 落在售后单上。
  6. */
  7. namespace bizGhs\order\services;
  8. use bizGhs\custom\classes\CustomClass;
  9. use bizGhs\custom\services\GhsRechargeSettleService;
  10. use bizGhs\order\classes\OrderClass;
  11. use bizGhs\order\classes\RefundOrderClass;
  12. use bizGhs\order\classes\RefundOrderItemClass;
  13. use bizGhs\product\classes\ProductClass;
  14. use bizGhs\shop\classes\MainClass;
  15. use bizGhs\stock\classes\StockRecordClass;
  16. use bizHd\cg\classes\CgRefundClass;
  17. use bizHd\purchase\classes\PurchaseClass;
  18. use common\components\dict;
  19. use common\components\util;
  20. use Yii;
  21. class NextDayRefundService
  22. {
  23. const SAME_DAY_YES = 1;
  24. const SAME_DAY_NO = 0;
  25. /** 资金未确认 */
  26. const FUND_UNUSED = 0;
  27. /** 原路退回 */
  28. const FUND_ORIGINAL = 1;
  29. /** 返充到余额 */
  30. const FUND_BALANCE = 2;
  31. /** 线下自行转账(原「仅记账」) */
  32. const FUND_NONE = 3;
  33. /** 与 FUND_NONE 同义,便于语义阅读 */
  34. const FUND_OFFLINE = 3;
  35. /**
  36. * 是否有有效支付时间(售后唯一时间依据;无支付时间不能售后)
  37. */
  38. public static function hasValidPayTime($order)
  39. {
  40. if (empty($order)) {
  41. return false;
  42. }
  43. $payTime = $order->payTime ?? '';
  44. return !empty($payTime) && $payTime !== '0000-00-00 00:00:00';
  45. }
  46. /**
  47. * 售后入口校验:无支付时间直接拦截
  48. */
  49. public static function assertCanRefund($order)
  50. {
  51. if (empty($order)) {
  52. util::fail('没有原订单');
  53. }
  54. if (!self::hasValidPayTime($order)) {
  55. util::fail('订单无支付时间,不能售后');
  56. }
  57. }
  58. /**
  59. * 原单是否可走隔天售后(即原「冲销」路径):
  60. * - 非当天支付(仅看 payTime);或
  61. * - 挂账已结清;或
  62. * - 已结账单(当天已结账也按冲销处理,不走普通售后)
  63. * @return array{ok:bool,reason:string,sameDay:int}
  64. */
  65. public static function checkEligible($order)
  66. {
  67. if (empty($order)) {
  68. return ['ok' => false, 'reason' => '没有原订单', 'sameDay' => self::SAME_DAY_YES, 'hasPayTime' => 0];
  69. }
  70. // 只用支付时间;无支付时间本单不能售后(由 assertCanRefund 拦截)
  71. if (!self::hasValidPayTime($order)) {
  72. return [
  73. 'ok' => false,
  74. 'reason' => '订单无支付时间,不能售后',
  75. 'sameDay' => self::SAME_DAY_YES,
  76. 'hasPayTime' => 0,
  77. ];
  78. }
  79. $payTime = $order->payTime;
  80. $isToday = date('Y-m-d', strtotime($payTime)) === date('Y-m-d');
  81. $debtPrice = bcadd((string)($order->debtPrice ?? '0'), '0', 2);
  82. $remainDebt = bcadd((string)($order->remainDebtPrice ?? '0'), '0', 2);
  83. $debtCleared = bccomp($debtPrice, '0', 2) > 0 && bccomp($remainDebt, '0', 2) === 0;
  84. // 已结账:当天也强制走冲销(sameDay=0),与隔天同一套资金/金额逻辑
  85. $orderCleared = !empty($order->clearId);
  86. if ($isToday && !$debtCleared && !$orderCleared) {
  87. return [
  88. 'ok' => false,
  89. 'reason' => '当天未结账订单请走普通售后;已结账/挂账结清/隔天可售后',
  90. 'sameDay' => self::SAME_DAY_YES,
  91. 'hasPayTime' => 1,
  92. ];
  93. }
  94. return [
  95. 'ok' => true,
  96. 'reason' => '',
  97. 'sameDay' => self::SAME_DAY_NO,
  98. 'suggestFundBalance' => true,
  99. 'orderCleared' => $orderCleared ? 1 : 0,
  100. 'hasPayTime' => 1,
  101. ];
  102. }
  103. /**
  104. * 创建/审核时是否必须按隔天售后(sameDay=0):
  105. * 与 checkEligible 对齐——非当天 / 已结账单 / 挂账已结清,均不可再走当天售后。
  106. * 无支付时间返回 false(须先走 assertCanRefund)。
  107. */
  108. public static function mustUseNextDay($order)
  109. {
  110. if (!self::hasValidPayTime($order)) {
  111. return false;
  112. }
  113. $check = self::checkEligible($order);
  114. return !empty($check['ok']);
  115. }
  116. /**
  117. * 原单是否为微信/支付宝线上支付(隔天也可原路退回的前提)
  118. */
  119. public static function isOnlinePayOrder($order)
  120. {
  121. if (empty($order)) {
  122. return false;
  123. }
  124. $payWay = intval($order->payWay ?? 0);
  125. $onlinePay = intval($order->onlinePay ?? dict::getDict('onlinePay', 'not'));
  126. $wxPay = dict::getDict('payWay', 'wxPay');
  127. $aliPay = dict::getDict('payWay', 'alipay');
  128. return $onlinePay == dict::getDict('onlinePay', 'yes')
  129. && in_array($payWay, [$wxPay, $aliPay], true);
  130. }
  131. /**
  132. * 隔天售后未选手选资金时的默认值:线上默认原路,其它默认返余额。
  133. */
  134. public static function defaultNextDayFundType($order)
  135. {
  136. return self::isOnlinePayOrder($order) ? self::FUND_ORIGINAL : self::FUND_BALANCE;
  137. }
  138. /**
  139. * 当天售后未选手选资金时的默认值:线上原路;余额/挂账返余额;线下默认线下转账。
  140. */
  141. public static function defaultSameDayFundType($order)
  142. {
  143. if (self::isOnlinePayOrder($order)) {
  144. return self::FUND_ORIGINAL;
  145. }
  146. $payWay = intval($order->payWay ?? 0);
  147. $balancePay = dict::getDict('payWay', 'balancePay');
  148. $debtPay = dict::getDict('payWay', 'debtPay');
  149. if ($payWay === $balancePay || $payWay === $debtPay) {
  150. return self::FUND_BALANCE;
  151. }
  152. return self::FUND_NONE;
  153. }
  154. /**
  155. * 解析资金方式(当天/隔天通用):挂账/余额强制返余额;线上可选原路/返余额/线下转账。
  156. * $hasRelateOrder=false 表示无原单退款,不可原路退。
  157. */
  158. public static function resolveFundType($fundType, $order, $hasRelateOrder = true)
  159. {
  160. $fundType = intval($fundType);
  161. // 无原单:只能返余额或线下自行转账
  162. if (!$hasRelateOrder || empty($order)) {
  163. if ($fundType === self::FUND_ORIGINAL) {
  164. util::fail('无原单退款不能原路退回');
  165. }
  166. if (!in_array($fundType, [self::FUND_BALANCE, self::FUND_NONE], true)) {
  167. return self::FUND_BALANCE;
  168. }
  169. return $fundType;
  170. }
  171. $payWay = intval($order->payWay ?? 0);
  172. $debtPay = dict::getDict('payWay', 'debtPay');
  173. $balancePay = dict::getDict('payWay', 'balancePay');
  174. // 挂账/余额付款:资金落到返充余额
  175. if ($payWay === $debtPay || $payWay === $balancePay) {
  176. return self::FUND_BALANCE;
  177. }
  178. // 线上付款:可选原路 / 返余额 / 线下转账
  179. if (self::isOnlinePayOrder($order)) {
  180. if (!in_array($fundType, [self::FUND_ORIGINAL, self::FUND_BALANCE, self::FUND_NONE], true)) {
  181. util::fail('请选择资金处理方式');
  182. }
  183. return $fundType;
  184. }
  185. // 线下付款:返余额或线下自行转账
  186. if (!in_array($fundType, [self::FUND_BALANCE, self::FUND_NONE], true)) {
  187. return self::FUND_BALANCE;
  188. }
  189. return $fundType;
  190. }
  191. /**
  192. * 生成写入售后单的资金快照:fundType + onlinePay + payWay(落 xhRefund/xhCgRefund)。
  193. * onlinePay:原路且走线上通道为 2,否则 1;payWay:原路/线下转账的具体渠道。
  194. *
  195. * @param int $fundTypeInput 前端/上游传入,0 表示按 sameDay 自动默认
  196. * @param object|null $order 原销售单;无原单传 null
  197. * @param bool $hasRelateOrder 是否有关联原单
  198. * @param array $options sameDay、payWay(线下转账选手选渠道)
  199. * @return array{fundType:int,onlinePay:int,payWay:int}
  200. */
  201. public static function buildRefundFundSnapshot($fundTypeInput, $order = null, $hasRelateOrder = true, $options = [])
  202. {
  203. $fundTypeInput = intval($fundTypeInput);
  204. $sameDay = intval($options['sameDay'] ?? self::SAME_DAY_YES);
  205. $onlineNot = intval(dict::getDict('onlinePay', 'not'));
  206. $onlineYes = intval(dict::getDict('onlinePay', 'yes'));
  207. if ($fundTypeInput === self::FUND_UNUSED && $hasRelateOrder && !empty($order)) {
  208. $fundTypeInput = ($sameDay === self::SAME_DAY_NO)
  209. ? self::defaultNextDayFundType($order)
  210. : self::defaultSameDayFundType($order);
  211. }
  212. $fundType = self::resolveFundType($fundTypeInput, $order, $hasRelateOrder);
  213. // 返充余额:渠道固定为余额,非线上原路
  214. if ($fundType === self::FUND_BALANCE) {
  215. return [
  216. 'fundType' => self::FUND_BALANCE,
  217. 'onlinePay' => $onlineNot,
  218. 'payWay' => intval(dict::getDict('payWay', 'balancePay')),
  219. ];
  220. }
  221. // 线下自行转账:优先用选手选 payWay(微信/支付宝/现金/银行卡)
  222. if ($fundType === self::FUND_NONE) {
  223. $payWay = self::normalizeOfflinePayWay($options['payWay'] ?? null, $order);
  224. return [
  225. 'fundType' => self::FUND_NONE,
  226. 'onlinePay' => $onlineNot,
  227. 'payWay' => $payWay,
  228. ];
  229. }
  230. // 原路退回:线上微信/支付宝标记 onlinePay=2;否则记线下原路渠道
  231. if ($fundType === self::FUND_ORIGINAL && !empty($order)) {
  232. $orderPayWay = intval($order->payWay ?? 0);
  233. if (self::isOnlinePayOrder($order)) {
  234. return [
  235. 'fundType' => self::FUND_ORIGINAL,
  236. 'onlinePay' => $onlineYes,
  237. 'payWay' => $orderPayWay,
  238. ];
  239. }
  240. return [
  241. 'fundType' => self::FUND_ORIGINAL,
  242. 'onlinePay' => $onlineNot,
  243. 'payWay' => $orderPayWay,
  244. ];
  245. }
  246. return [
  247. 'fundType' => self::FUND_UNUSED,
  248. 'onlinePay' => $onlineNot,
  249. 'payWay' => 0,
  250. ];
  251. }
  252. /**
  253. * 线下转账渠道:允许 0微信/1支付宝/4现金/5银行卡;非法则回落原单或现金
  254. */
  255. protected static function normalizeOfflinePayWay($payWay, $order = null)
  256. {
  257. $allowed = [
  258. intval(dict::getDict('payWay', 'wxPay')),
  259. intval(dict::getDict('payWay', 'alipay')),
  260. intval(dict::getDict('payWay', 'cash')),
  261. intval(dict::getDict('payWay', 'bankCard')),
  262. ];
  263. if ($payWay !== null && $payWay !== '' && in_array(intval($payWay), $allowed, true)) {
  264. return intval($payWay);
  265. }
  266. if (!empty($order)) {
  267. $orderPayWay = intval($order->payWay ?? -1);
  268. if (in_array($orderPayWay, $allowed, true)) {
  269. return $orderPayWay;
  270. }
  271. }
  272. return intval(dict::getDict('payWay', 'cash'));
  273. }
  274. /**
  275. * GHS 侧隔天通过:累计 nextDayTkPrice,按 fundType 处理资金,不改 actPrice。
  276. * @return string 返余额后的客户余额(非返余额则为当前余额)
  277. */
  278. public static function applyGhsAmountAndFund($refund, $order)
  279. {
  280. $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
  281. $order->nextDayTkPrice = bcadd((string)($order->nextDayTkPrice ?? '0'), $refundPrice, 2);
  282. $order->refund = OrderClass::REFUND_YES;
  283. $order->save(false, ['nextDayTkPrice', 'refund']);
  284. $customId = intval($order->customId ?? 0);
  285. $custom = CustomClass::getLockById($customId);
  286. if (empty($custom)) {
  287. util::fail('没有找到客户');
  288. }
  289. $buyAmount = bcsub((string)($custom->buyAmount ?? '0'), $refundPrice, 2);
  290. if (bccomp($buyAmount, '0', 2) < 0) {
  291. $buyAmount = '0.00';
  292. }
  293. $custom->buyAmount = $buyAmount;
  294. $custom->save(false, ['buyAmount']);
  295. $fundType = intval($refund->fundType ?? self::FUND_UNUSED);
  296. $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
  297. if ($fundType === self::FUND_BALANCE) {
  298. $pair = GhsRechargeSettleService::lockAccountPair($custom);
  299. CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, $order);
  300. $custom = CustomClass::getById($customId, true);
  301. $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
  302. }
  303. // FUND_ORIGINAL:网关退款在采购侧执行;FUND_NONE:线下自行转账(只记账)
  304. return $balance;
  305. }
  306. /**
  307. * HD 采购侧隔天通过:累计 nextDayTkPrice;原路退走拉卡拉,不改 actPrice。
  308. */
  309. public static function applyCgAmountAndFund($cgRefund, $cg)
  310. {
  311. $refundPrice = bcadd((string)($cgRefund->refundPrice ?? '0'), '0', 2);
  312. $cg->nextDayTkPrice = bcadd((string)($cg->nextDayTkPrice ?? '0'), $refundPrice, 2);
  313. $cg->refund = PurchaseClass::REFUND_YES;
  314. $cg->save(false, ['nextDayTkPrice', 'refund']);
  315. $fundType = intval($cgRefund->fundType ?? self::FUND_UNUSED);
  316. if ($fundType === self::FUND_ORIGINAL) {
  317. CgRefundClass::nextDayOriginalOnlineRefund($cgRefund, $cg);
  318. }
  319. $ghsId = intval($cg->ghsId ?? 0);
  320. if ($ghsId > 0) {
  321. $ghs = \bizHd\ghs\classes\GhsClass::getLockById($ghsId);
  322. if (!empty($ghs) && isset($ghs->expendAmount)) {
  323. $expend = bcsub((string)($ghs->expendAmount ?? '0'), $refundPrice, 2);
  324. if (bccomp($expend, '0', 2) < 0) {
  325. $expend = '0.00';
  326. }
  327. $ghs->expendAmount = $expend;
  328. $ghs->save(false, ['expendAmount']);
  329. }
  330. }
  331. }
  332. /**
  333. * 无原单退款通过:加库存(退货退款)、累计门店支出、扣客户消费、按 fundType 返余额/仅记账。
  334. * 不写订单 nextDayTkPrice(无关联销售单)。
  335. */
  336. public static function passFreeRefund($refund)
  337. {
  338. if (empty($refund)) {
  339. util::fail('退款单不存在');
  340. }
  341. if (intval($refund->status) === RefundOrderClass::STATUS_COMPLETE) {
  342. util::fail('已通过');
  343. }
  344. if (intval($refund->status) === RefundOrderClass::STATUS_REJECT) {
  345. util::fail('已驳回');
  346. }
  347. if (intval($refund->status) === RefundOrderClass::STATUS_CANCEL) {
  348. util::fail('已取消');
  349. }
  350. $refund->status = RefundOrderClass::STATUS_COMPLETE;
  351. $refund->passTime = date('Y-m-d H:i:s');
  352. $refund->save();
  353. $refundSn = $refund->orderSn ?? '';
  354. $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
  355. $refundType = intval($refund->refundType ?? RefundOrderClass::REFUND_TYPE_MONEY_GOOD);
  356. $shopId = intval($refund->shopId ?? 0);
  357. $mainId = intval($refund->mainId ?? 0);
  358. $sjId = intval($refund->sjId ?? 0);
  359. $customName = $refund->customName ?? '';
  360. // 有明细则置通过;仅「退货退款」回库(库存不退只记账不改库存)
  361. $refundItemList = RefundOrderItemClass::getAllByCondition(['orderSn' => $refundSn], null, '*', null, true);
  362. if ($refundType === RefundOrderClass::REFUND_TYPE_MONEY_GOOD && empty($refundItemList)) {
  363. util::fail('没有找到退款花材');
  364. }
  365. if (!empty($refundItemList)) {
  366. foreach ($refundItemList as $val) {
  367. $val->status = 1;
  368. $val->save();
  369. if ($refundType !== RefundOrderClass::REFUND_TYPE_MONEY_GOOD) {
  370. continue;
  371. }
  372. $productId = intval($val->productId ?? 0);
  373. $ptItemId = intval($val->itemId ?? 0);
  374. $num = bcadd((string)($val->xhNum ?? '0'), '0', 2);
  375. if (bccomp($num, '0', 2) <= 0 || $productId <= 0) {
  376. continue;
  377. }
  378. if (intval($val->xhUnitType ?? 0) === 1) {
  379. $smallNum = $num;
  380. $bigNum = 0;
  381. } else {
  382. $smallNum = 0;
  383. $bigNum = $num;
  384. }
  385. $stockInfo = ProductClass::addStock($productId, $bigNum, $smallNum);
  386. ProductClass::tryRecoverLimitBuyWhenStockRollbackFromZero($productId, $stockInfo['oldStock'] ?? 0);
  387. $oldStock = $stockInfo['oldStock'] ?? 0;
  388. $newStock = $stockInfo['newStock'] ?? 0;
  389. $recordData = [
  390. 'relateName' => $customName,
  391. 'itemNum' => $val->itemNum ?? 0,
  392. 'sjId' => $sjId,
  393. 'shopId' => $shopId,
  394. 'mainId' => $mainId,
  395. 'orderSn' => $refundSn,
  396. 'itemId' => $ptItemId,
  397. 'oldStock' => $oldStock,
  398. 'productId' => $productId,
  399. 'newStock' => $newStock,
  400. 'io' => 1,
  401. ];
  402. StockRecordClass::ghsRefundAddRecord($recordData);
  403. $val->itemStock = $oldStock;
  404. $val->newStock = $newStock;
  405. $val->save();
  406. }
  407. }
  408. $main = MainClass::getLockById($mainId);
  409. if (empty($main)) {
  410. util::fail('没有main信息');
  411. }
  412. $main->totalExpend = bcadd((string)($main->totalExpend ?? '0'), $refundPrice, 2);
  413. $main->totalRefund = bcadd((string)($main->totalRefund ?? '0'), $refundPrice, 2);
  414. $main->save();
  415. return self::applyFreeFund($refund);
  416. }
  417. /**
  418. * 无原单退款资金:扣减客户累计消费;返余额走成对账户,仅记账不动余额。
  419. * @return string 处理后的客户余额
  420. */
  421. public static function applyFreeFund($refund)
  422. {
  423. $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2);
  424. $customId = intval($refund->customId ?? 0);
  425. $custom = CustomClass::getLockById($customId);
  426. if (empty($custom)) {
  427. util::fail('没有找到客户');
  428. }
  429. $buyAmount = bcsub((string)($custom->buyAmount ?? '0'), $refundPrice, 2);
  430. if (bccomp($buyAmount, '0', 2) < 0) {
  431. $buyAmount = '0.00';
  432. }
  433. $custom->buyAmount = $buyAmount;
  434. $custom->save(false, ['buyAmount']);
  435. $fundType = intval($refund->fundType ?? self::FUND_UNUSED);
  436. $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
  437. if ($fundType === self::FUND_BALANCE) {
  438. $pair = GhsRechargeSettleService::lockAccountPair($custom);
  439. // 无原单:order 传 null,流水挂售后单号
  440. CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, null);
  441. $custom = CustomClass::getById($customId, true);
  442. $balance = bcadd((string)($custom->balance ?? '0'), '0', 2);
  443. }
  444. return $balance;
  445. }
  446. /**
  447. * 按时段汇总成功隔天售后金额(收入/销量统计扣减用)
  448. */
  449. public static function sumAmountByMainAndTime($mainId, $startTime, $endTime)
  450. {
  451. $sql = "SELECT COALESCE(SUM(refundPrice),0) AS total FROM xhRefund
  452. WHERE mainId=:mainId AND status=:status AND sameDay=:sameDay
  453. AND passTime BETWEEN :start AND :end";
  454. $row = Yii::$app->db->createCommand($sql, [
  455. ':mainId' => $mainId,
  456. ':status' => RefundOrderClass::STATUS_COMPLETE,
  457. ':sameDay' => self::SAME_DAY_NO,
  458. ':start' => $startTime,
  459. ':end' => $endTime,
  460. ])->queryOne();
  461. return bcadd($row['total'] ?? '0', '0', 2);
  462. }
  463. /**
  464. * 渠道收入对冲:拉取隔天成功售后及原单支付字段
  465. */
  466. public static function listForChannelIncomeDeduct($mainId, $startTime, $endTime)
  467. {
  468. $sql = "SELECT r.refundPrice AS amount, r.fundType, o.id AS orderId,
  469. o.debtPrice, o.remainDebtPrice, o.onlinePay, o.payWay AS orderPayWay, o.payWay
  470. FROM xhRefund r
  471. LEFT JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
  472. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  473. AND IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end";
  474. $rows = Yii::$app->db->createCommand($sql, [
  475. ':mainId' => $mainId,
  476. ':status' => RefundOrderClass::STATUS_COMPLETE,
  477. ':sameDay' => self::SAME_DAY_NO,
  478. ':start' => $startTime,
  479. ':end' => $endTime,
  480. ])->queryAll();
  481. return $rows ?: [];
  482. }
  483. /**
  484. * 按客户汇总隔天退款金额
  485. */
  486. public static function sumAmountGroupByCustom($mainId, $startTime, $endTime)
  487. {
  488. $sql = "SELECT r.customId, COALESCE(SUM(r.refundPrice),0) AS total FROM xhRefund r
  489. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  490. AND IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end
  491. GROUP BY r.customId";
  492. $rows = Yii::$app->db->createCommand($sql, [
  493. ':mainId' => $mainId,
  494. ':status' => RefundOrderClass::STATUS_COMPLETE,
  495. ':sameDay' => self::SAME_DAY_NO,
  496. ':start' => $startTime,
  497. ':end' => $endTime,
  498. ])->queryAll();
  499. $map = [];
  500. foreach ($rows as $row) {
  501. $map[$row['customId']] = bcadd($row['total'] ?? '0', '0', 2);
  502. }
  503. return $map;
  504. }
  505. /**
  506. * 按花材汇总隔天退货数量与金额(退货退款明细)
  507. */
  508. public static function sumItemByProduct($mainId, $startTime, $endTime)
  509. {
  510. $sql = "SELECT i.productId, COALESCE(SUM(i.xhNum),0) AS num, COALESCE(SUM(i.xhPrice),0) AS amount
  511. FROM xhRefundItem i
  512. INNER JOIN xhRefund r ON r.orderSn = i.orderSn
  513. WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
  514. AND r.refundType=:rtype
  515. AND IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end
  516. GROUP BY i.productId";
  517. $rows = Yii::$app->db->createCommand($sql, [
  518. ':mainId' => $mainId,
  519. ':status' => RefundOrderClass::STATUS_COMPLETE,
  520. ':sameDay' => self::SAME_DAY_NO,
  521. ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
  522. ':start' => $startTime,
  523. ':end' => $endTime,
  524. ])->queryAll();
  525. $map = [];
  526. foreach ($rows as $row) {
  527. $map[$row['productId']] = [
  528. 'num' => bcadd($row['num'] ?? '0', '0', 2),
  529. 'amount' => bcadd($row['amount'] ?? '0', '0', 2),
  530. ];
  531. }
  532. return $map;
  533. }
  534. }