$customId, 'status' => PurchaseClearClass::STATUS_AWAIT_PAY], null, '*', null, true); if (empty($list)) { return; } foreach ($list as $item) { $item->status = PurchaseClearClass::STATUS_EXPIRE; $item->save(); OrderCgClearClass::markExpireByClearId($item->id); } } /** 汇总结账单下各 OrderCgClear.amount(实销合计,支持部分销单) */ public static function sumClearAmountByClearId($clearId) { $clearId = intval($clearId); if ($clearId <= 0) { return '0.00'; } $list = OrderCgClearClass::getAllByCondition(['clearId' => $clearId], null, '*', null, true); $total = '0.00'; if (!empty($list)) { foreach ($list as $row) { $total = bcadd($total, $row->amount ?? 0, 2); } } return $total; } /** * 按每单指定 clearAmount 建结账单(充值 FIFO 销账、末单可部分销 remainDebtPrice) * * @param array $post 同 clear() * @param array $orderRows [['orderId'=>,'clearAmount'=>,'orderSn'=>], ...] */ public static function clearWithAmountMap($post, $orderRows, $sjId, $shopId) { if (empty($orderRows)) { util::fail('请选择订单'); } $ids = []; $amountMap = []; foreach ($orderRows as $row) { $orderId = intval($row['orderId'] ?? 0); if ($orderId <= 0) { continue; } $ids[] = $orderId; $amountMap[$orderId] = bcadd((string)($row['clearAmount'] ?? 0), '0', 2); } if (empty($ids)) { util::fail('请选择订单'); } $idText = json_encode(array_map(function ($id) { return ['id' => $id]; }, $ids)); $post['modifyPrice'] = $post['modifyPrice'] ?? array_sum(array_map('floatval', $amountMap)); $clear = self::clear($post, $idText, $sjId, $shopId, $amountMap); return $clear; } //订单结算 ssh 2021.3.14 public static function clear($post, $idText, $sjId, $shopId, $orderAmountMap = []) { $customId = $post['customId'] ?? 0; $data = json_decode($idText, true); $ids = array_column($data, 'id'); if (empty($ids)) { util::fail('请选择交易记录'); } $list = OrderClass::getByIds($ids); if (empty($list)) { util::fail('请选择订单'); } $modifyPrice = bcadd((string)($post['modifyPrice'] ?? '0'), '0', 2); $amount = '0.00'; if (!empty($orderAmountMap)) { // 充值 FIFO 销账:prePrice=Σ每单销账额(对照 hd Settle,勿用订单全款算优惠) foreach ($orderAmountMap as $mayClear) { $amount = bcadd($amount, (string)$mayClear, 2); } } foreach ($list as $key => $val) { if ($val['sjId'] != $sjId) { util::fail('只能结算自己的订单哦'); } if ($val['debt'] == 0) { util::fail('有订单已经结算过了'); } if ($val['customId'] != $customId) { util::fail('每次只能结算一个客户的订单'); } if (empty($orderAmountMap)) { $amount = bcadd($amount, $val['remainDebtPrice'], 2); } } $custom = CustomClass::getLockById($customId); if (empty($custom)) { util::fail('没有找到客户'); } // 建结账单前:历史挂账并入净 balance(关键词 ensure_before_recharge_clear) \bizGhs\custom\classes\AccountMoneyClass::ensureCustomMoneyReady($custom, true); $ghsId = $custom->ghsId; $ghs = GhsClass::getLockById($ghsId); if (empty($ghs)) { util::fail('没有找到供货商'); } \bizGhs\custom\classes\AccountMoneyClass::ensureGhsMoneyReady($ghs, true); $ghsShopId = $ghs['shopId'] ?? 0; $ghsShop = ShopClass::getById($ghsShopId, true); $ghsShopName = $ghsShop->shopName ?? ''; $ghsShopName = $ghsShopName == '首店' ? '总店' : $ghsShopName; $orderSn = orderSn::getPurchaseClearSn(); //默认24小时过期 $deadline = date("Y-m-d H:i:s", time() + 24 * 60 * 60); //天天鲜花12小时之后 if ($ghsShopId == 763 || $ghsShopId == 795 || $ghsShopId == 1405) { $deadline = date("Y-m-d H:i:s", time() + 10 * 60 * 60); } //开发环境10个小时过期 if (getenv('YII_ENV') != 'production') { $deadline = date("Y-m-d H:i:s", time() + 10 * 60 * 60); } //惠雅鲜花 结账单过期时间设置为30天 if ($ghsShopId == 520 || $ghsShopId == 1489 || $ghsShopId == 2164 || $ghsShopId == 2167) { $deadline = date("Y-m-d H:i:s", time() + 30 * 24 * 60 * 60); } //昱成花卉 结账单过期时间设置为30天 if ($ghsShopId == 8596) { $deadline = date("Y-m-d H:i:s", time() + 30 * 24 * 60 * 60); } //花大苪 结账单过期时间设置为30天 if ($ghsShopId == 8249) { $deadline = date("Y-m-d H:i:s", time() + 30 * 24 * 60 * 60); } //丰行鲜花 结账单过期时间设置为30天 if ($ghsShopId == 7855) { $deadline = date("Y-m-d H:i:s", time() + 30 * 24 * 60 * 60); } $payWay = isset($post['payWay']) && is_numeric($post['payWay']) ? $post['payWay'] : dict::getDict('payWay', 'unPay'); $remark = $post['remark'] ?? ''; $onlinePay = isset($post['onlinePay']) && is_numeric($post['onlinePay']) ? intval($post['onlinePay']) : intval(dict::getDict('onlinePay', 'not')); $clearData = [ 'prePrice' => $amount, 'actPrice' => $modifyPrice, 'realPrice' => $modifyPrice, 'ghsId' => $ghsId, 'ghsName' => $ghs['name'] ?? '', 'ghsMobile' => $ghs['mobile'] ?? '', 'ghsAvatar' => $ghs['avatar'] ?? '', 'ghsAddress' => $ghs['address'] ?? '', 'ghsShopAdminId' => $post['ghsShopAdminId'], 'ghsShopId' => $post['ghsShopId'] ?? 0, 'ghsShopAdminName' => $post['ghsShopAdminName'], 'sjId' => $sjId, 'shopId' => $shopId, 'orderSn' => $orderSn, 'status' => 1, 'clearStyle' => dict::getDict('clearStyle', 'gys2Hd'), 'customId' => $customId, 'customName' => $custom['name'] ?? '', 'customShopId' => $custom['shopId'] ?? 0, 'customAvatar' => $custom['avatar'] ?? '', 'customMobile' => $custom['mobile'] ?? '', 'customAddress' => $custom['address'] ?? '', 'deadline' => $deadline, 'payWay' => $payWay, 'onlinePay' => $onlinePay, 'num' => count($ids), 'ghsShopName' => $ghsShopName, 'hasNoticeCustom' => 0, 'remark' => $remark, // 生成并保存 salt 字段,用于结账单的验签和防篡改分享链接 'salt' => $post['salt'] ?? \common\components\stringUtil::charsShuffleLowerCase(10), ]; if (!empty($orderAmountMap)) { // 自动充值销账:结账单金额=本次来款池,不产生虚假「优惠」 $modifyPrice = $amount; } if (bccomp($modifyPrice, $amount, 2) < 0) { $clearData['discountAmount'] = bcsub($amount, $modifyPrice, 2); $clearData['discountType'] = dict::getDict('discountType', 'discount'); } if (bccomp($modifyPrice, $amount, 2) > 0) { //noticeUtil::push("结账金额大于总欠款金额 {$modifyPrice} {$amount}", '15280215347'); util::fail('结账金额大于总欠款金额'); } $return = PurchaseClearClass::add($clearData, true); $pairs = OrderCgClearClass::buildPairsFromOrders($list); // 充值销账:每单销账金额可小于 remainDebtPrice(部分销) if (!empty($orderAmountMap)) { foreach ($pairs as $k => $pair) { $oid = intval($pair['orderId'] ?? 0); if ($oid > 0 && isset($orderAmountMap[$oid])) { $pairs[$k]['amount'] = $orderAmountMap[$oid]; } } } OrderCgClearClass::bindRelations($return, $pairs); $complete = $post['complete'] ?? 0; if ($complete == 1) { self::confirmClear($return, $payWay); } return $return; } /** * 净余额模型销账:只减订单 remainDebtPrice,不调 clearDebtAmountReduce、不二次扣 balance。 * 充值/来款链路请只调本方法(GhsRechargeSettleService)。 */ public static function applyNetBalanceClear($clear, $payWay = 0, $options = []) { return self::confirmClear($clear, $payWay, array_merge([ 'paymentSettleOnly' => true, ], $options)); } /** * 确认结账(手工结账单等仍可能走完整链路;来款自动销账请用 applyNetBalanceClear) * * $options['paymentSettleOnly'] 只减订单待结 * $options['skipCashMoney'] 现金已在 rechargeBalance 记过 */ public static function confirmClear($clear, $payWay, $options = []) { if ($clear->status == 2) { util::fail('已结过账了'); } if ($clear->status == 3) { util::fail('已取消了'); } $current = time(); if ($current >= strtotime($clear->deadline)) { util::fail('已过期,请手动取消'); } $clear->payWay = $payWay; $clear->payTime = date("Y-m-d H:i:s"); $clear->status = 2; // 结清时同步 onlinePay;显式传入 not(1) 时不得被微信/支付宝兜底覆盖 if (array_key_exists('onlinePay', $options) && is_numeric($options['onlinePay'])) { $clear->onlinePay = intval($options['onlinePay']); } elseif (in_array(intval($payWay), [ intval(dict::getDict('payWay', 'wxPay')), intval(dict::getDict('payWay', 'alipay')), ], true)) { $clear->onlinePay = intval(dict::getDict('onlinePay', 'yes')); } $clear->save(); $clearId = $clear->id ?? 0; $customId = $clear->customId ?? 0; $orderSn = $clear->orderSn ?? ''; $customName = $clear->customName ?? ''; $custom = CustomClass::getLockById($customId); if (empty($custom)) { util::fail('没有找到客户'); } $ghsId = $custom->ghsId; $ghs = GhsClass::getLockById($ghsId); if (empty($ghs)) { util::fail('没有找到供货商'); } // true=净余额模型下的销单;false=旧链路(仍调 clearDebtAmountReduce,complete=1 当场结清等) $paymentSettleOnly = !empty($options['paymentSettleOnly']); $orderSettleRows = OrderCgClearClass::getAllByCondition(['clearId' => $clearId], null, '*', null, true); if (empty($orderSettleRows)) { util::fail('数据错误'); } $clearTime = date("Y-m-d H:i:s"); $totalSettled = '0.00'; // 手工结账单可能 actPrice < 明细合计(抹零);合并后销单以 actPrice 为本次付款上限 $settleBudget = bcadd((string)($clear->actPrice ?? '0'), '0', 2); if ($paymentSettleOnly && bccomp($settleBudget, '0', 2) <= 0) { $settleBudget = bcadd((string)($clear->prePrice ?? '0'), '0', 2); } foreach ($orderSettleRows as $os) { $orderId = intval($os->orderId ?? 0); $cgId = intval($os->cgId ?? 0); $mayClear = bcadd((string)($os->amount ?? 0), '0', 2); if ($orderId <= 0 || bccomp($mayClear, '0', 2) <= 0) { continue; } if ($paymentSettleOnly && bccomp($settleBudget, '0', 2) > 0) { $budgetLeft = bcsub($settleBudget, $totalSettled, 2); if (bccomp($budgetLeft, '0', 2) <= 0) { break; } if (bccomp($mayClear, $budgetLeft, 2) > 0) { $mayClear = $budgetLeft; } } $order = OrderClass::getById($orderId, true); if (empty($order) || ($order->debt ?? 0) == 0) { continue; } // 按 OrderCgClear.amount 部分或足额减少待结,不是一律置 remainDebtPrice=0 $newRemain = bcsub((string)($order->remainDebtPrice ?? 0), $mayClear, 2); $orderUpdate = [ 'remainDebtPrice' => bccomp($newRemain, '0', 2) > 0 ? $newRemain : '0.00', 'clearTime' => $clearTime, ]; if (bccomp($newRemain, '0', 2) <= 0) { $orderUpdate['debt'] = 0; $orderUpdate['clearId'] = $clearId; } OrderClass::updateById($orderId, $orderUpdate); // 批发单销账后立即按 xhGhsOrder 结果写回 xhCg,与充值 FIFO 同一事务内完成 self::syncHdPurchaseAfterOrderClear($orderId, $cgId, $clearId, $clearTime); $totalSettled = bcadd($totalSettled, $mayClear, 2); $os->amount = $mayClear; $os->status = 1; $os->save(); } if (!$paymentSettleOnly) { // 未合并或旧当场结清:仍减少 debtAmount 字段(合并后新业务勿走此分支) $amount = $clear->prePrice ?? 0; CustomClass::clearDebtAmountReduce($custom, $ghs, $amount, $clear); \bizHd\ghs\classes\GhsClass::clearDebtAmountReduce($ghs, $custom, $amount, $clear); } else { // 挂账已在 balance,只根据净余额刷新欠款标记;扣款在 clearConsumeBalance $amount = $totalSettled; $custom->isDebt = bccomp($custom->balance ?? '0', '0', 2) < 0 ? CustomClass::IS_DEBT_YES : CustomClass::IS_DEBT_NO; $custom->save(false, ['isDebt']); $ghs->debt = bccomp($custom->balance ?? '0', '0', 2) < 0 ? 2 : 1; $ghs->save(false, ['debt']); } //现金余额增加(充值销账链路里现金已在 rechargeBalance 记过,需 skipCashMoney) $skipCashMoney = !empty($options['skipCashMoney']); if (!$skipCashMoney && $payWay == dict::getDict('payWay', 'cash')) { $mainId = $ghs->mainId ?? 0; $main = MainClass::getLockById($mainId); if (empty($main)) { util::fail('没有商家信息'); } $moneyBalance = bcadd($main->money, $amount, 2); $main->money = $moneyBalance; $main->save(); $moneyEvent = $customName . "现金结账" . floatval($amount) . "元,单号 {$orderSn})"; $moneyRemark = ''; $capitalType = dict::getDict('capitalType', 'ghsXsClear', 'id'); $change = [ 'relateId' => $clearId, 'amount' => $amount, 'balance' => $moneyBalance, 'io' => 1, 'mainId' => $mainId, 'capitalType' => $capitalType, 'ptStyle' => dict::getDict('ptStyle', 'ghs'), 'event' => $moneyEvent, 'remark' => $moneyRemark, ]; ShopMoneyChangeClass::addData($change); } OrderCgClearClass::markDoneByClearId($clearId); } /** * 批发订单销账后同步花店采购单待结(充值 FIFO、结账单确认等)。 * 以销账后的 xhGhsOrder 为准写回 xhCg,避免两边各自加减导致 remainDebtPrice 不一致。 * * @param int $orderId 批发销售单 id(xhGhsOrder.id) * @param int $cgId 结账单明细上的采购单 id,缺失时按 saleId 回查 xhCg * @param int $clearId 结账单 id * @param string $clearTime 销账时间 */ protected static function syncHdPurchaseAfterOrderClear($orderId, $cgId, $clearId, $clearTime) { $orderId = intval($orderId); $cgId = intval($cgId); $clearId = intval($clearId); if ($orderId <= 0) { return; } $order = OrderClass::getById($orderId, true); if (empty($order)) { noticeUtil::push("批发销账后同步采购单失败:未找到销售单 orderId:{$orderId} clearId:{$clearId}", '15280215347'); return; } $purchase = null; if ($cgId > 0) { $purchase = PurchaseClass::getById($cgId, true); } if (empty($purchase)) { $orderSn = $order->orderSn ?? ''; noticeUtil::push( "批发销账后同步采购单失败:未找到采购单 orderId:{$orderId} orderSn:{$orderSn} cgId:{$cgId} clearId:{$clearId}", '15280215347' ); return; } $orderRemain = bcadd((string)($order->remainDebtPrice ?? '0'), '0', 2); $orderDebtFlag = intval($order->debt ?? 0); $orderDebtPrice = bcadd((string)($order->debtPrice ?? $order->orderPrice ?? '0'), '0', 2); $saveFields = ['remainDebtPrice', 'clearTime', 'debt']; $purchase->remainDebtPrice = bccomp($orderRemain, '0', 2) > 0 ? $orderRemain : '0.00'; $purchase->clearTime = $clearTime; if ($orderDebtFlag === OrderClass::DEBT_YES) { $purchase->debt = PurchaseClass::DEBT_YES; } else { $purchase->debt = PurchaseClass::DEBT_NO; $orderClearId = intval($order->clearId ?? 0); if ($orderClearId > 0) { $purchase->clearId = $orderClearId; } elseif ($clearId > 0) { $purchase->clearId = $clearId; } $saveFields[] = 'clearId'; } if (bccomp((string)($purchase->debtPrice ?? '0'), '0', 2) <= 0 && bccomp($orderDebtPrice, '0', 2) > 0) { $purchase->debtPrice = $orderDebtPrice; $saveFields[] = 'debtPrice'; } $purchase->save(false, array_values(array_unique($saveFields))); } }