|
|
@@ -0,0 +1,380 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace bizGhs\custom\classes;
|
|
|
+
|
|
|
+use biz\ghs\classes\GhsClass as BizGhsClass;
|
|
|
+use bizGhs\ghs\classes\GhsBalanceChangeClass;
|
|
|
+use common\components\dict;
|
|
|
+use Yii;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 客户/供货商账户金额(表 xhGhsCustom、xhGhs)
|
|
|
+ *
|
|
|
+ * 背景:原 debtAmount=挂账累计、balance=充值余额,现统一为净 balance(正=有余额,负=待结)。
|
|
|
+ * 合并:balance = 原 balance - 原 debtAmount,debtAmount 置 0,balanceMerged=1 表示已合并且勿重复写说明流水。
|
|
|
+ * 写业务前调 ensureCustomMoneyReady / ensureGhsMoneyReady;列表/详情返回前调 formatMoneyForClient。
|
|
|
+ * 旧 App(appVersion<3)由 formatMoneyForClient 拆回待结+余额双字段;新 App 只读净 balance。
|
|
|
+ */
|
|
|
+class AccountMoneyClass
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * 新端协议版本号:请求头 appVersion >= 此值时,接口只返回净 balance,不再拆分待结/余额两列。
|
|
|
+ * 与前端 ghsApp/ghs/hdApp/ghsPad 的 request.js 中 appVersion 保持一致。
|
|
|
+ */
|
|
|
+ const APP_VERSION_NET_BALANCE = 3;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】获取当前 HTTP 请求里的客户端版本号,供 formatMoneyForClient 决定返回字段形态。
|
|
|
+ * 【调用时机】一般无需直接调;formatMoneyForClient 内部会自动读取。
|
|
|
+ * 【返回值】整数,默认 2(旧端);3 表示新端净余额协议。
|
|
|
+ */
|
|
|
+ public static function getClientAppVersion()
|
|
|
+ {
|
|
|
+ if (isset(Yii::$app->params['clientAppVersion'])) {
|
|
|
+ return (int)Yii::$app->params['clientAppVersion'];
|
|
|
+ }
|
|
|
+ $headers = Yii::$app->request->headers ?? null;
|
|
|
+ $ver = $headers ? (int)$headers->get('appVersion', 2) : 2;
|
|
|
+ Yii::$app->params['clientAppVersion'] = $ver;
|
|
|
+ return $ver;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】判断数据行是否带有 balanceMerged 字段(是否已执行 SQL 迁移)。
|
|
|
+ * 【调用时机】内部判断幂等标记;未迁移库时靠 debtAmount 是否为 0 代替。
|
|
|
+ * 【参数】$row 数组或 ActiveRecord
|
|
|
+ * 【返回值】true=表结构含 balanceMerged 列
|
|
|
+ */
|
|
|
+ public static function hasMergeFlagField($row)
|
|
|
+ {
|
|
|
+ if (is_array($row)) {
|
|
|
+ return array_key_exists('balanceMerged', $row);
|
|
|
+ }
|
|
|
+ return is_object($row) && method_exists($row, 'hasAttribute') && $row->hasAttribute('balanceMerged');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】判断该客户/供货商账户是否已经完成「挂账并入余额」。
|
|
|
+ * 【调用时机】合并前检查,避免重复合并、重复写说明流水。
|
|
|
+ * 【参数】$row 含 balanceMerged、debtAmount 的数组或模型
|
|
|
+ * 【返回值】true=已合并(或本无挂账)
|
|
|
+ */
|
|
|
+ public static function isMerged($row)
|
|
|
+ {
|
|
|
+ if (!self::hasMergeFlagField($row)) {
|
|
|
+ return bccomp(self::rawDebtAmount($row), '0', 2) == 0;
|
|
|
+ }
|
|
|
+ if (is_array($row)) {
|
|
|
+ return !empty($row['balanceMerged']);
|
|
|
+ }
|
|
|
+ return !empty($row->balanceMerged);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】内部取 debtAmount 原始值,供 isMerged / calcNetBalance 使用。
|
|
|
+ * 【说明】外部业务请用 getNetBalanceFromRow,不要直接读 debtAmount。
|
|
|
+ */
|
|
|
+ protected static function rawDebtAmount($row)
|
|
|
+ {
|
|
|
+ if (is_array($row)) {
|
|
|
+ return $row['debtAmount'] ?? '0.00';
|
|
|
+ }
|
|
|
+ return $row->debtAmount ?? '0.00';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】根据库里的 balance、debtAmount 计算「净账户余额」(合并前后均可用)。
|
|
|
+ * 【规则】未合并:净额 = balance - debtAmount(等价于旧逻辑下「余额减待结」后的真实资金位置);
|
|
|
+ * 已合并:净额 = balance(debtAmount 应为 0)。
|
|
|
+ * 【调用时机】统计总余额、采购列表算实欠、需要与旧 remainDebtAmount 对齐时。
|
|
|
+ * 【参数】$balance 充值余额字段;$debtAmount 挂账累计;$merged 是否已执行过合并
|
|
|
+ * 【返回值】字符串金额,bc 精度 2 位
|
|
|
+ */
|
|
|
+ public static function calcNetBalance($balance, $debtAmount, $merged = false)
|
|
|
+ {
|
|
|
+ $balance = $balance ?? '0.00';
|
|
|
+ $debtAmount = $debtAmount ?? '0.00';
|
|
|
+ if ($merged || bccomp($debtAmount, '0', 2) == 0) {
|
|
|
+ return $balance;
|
|
|
+ }
|
|
|
+ return bcsub($balance, $debtAmount, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】从一条客户或供货商记录中取出净账户余额(自动识别是否已合并)。
|
|
|
+ * 【调用时机】导出汇总、showTotalBalance、任何仍持有双字段模型的读逻辑。
|
|
|
+ * 【参数】$row 客户/供货商数组或 AR
|
|
|
+ * 【返回值】净 balance 字符串
|
|
|
+ */
|
|
|
+ public static function getNetBalanceFromRow($row)
|
|
|
+ {
|
|
|
+ if (is_array($row)) {
|
|
|
+ return self::calcNetBalance(
|
|
|
+ $row['balance'] ?? 0,
|
|
|
+ $row['debtAmount'] ?? 0,
|
|
|
+ self::isMerged($row)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return self::calcNetBalance(
|
|
|
+ $row->balance ?? 0,
|
|
|
+ $row->debtAmount ?? 0,
|
|
|
+ self::isMerged($row)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】得到当前账户「待结欠款」数额(正数),用于挂账额度校验、下单前是否超限。
|
|
|
+ * 【规则】净余额 >= 0 时返回 0;净余额 < 0 时返回 |净余额|(替代原 custom.debtAmount 与额度比较)。
|
|
|
+ * 【调用时机】客户自助下单校验 debtLimit;OrderService 欠款超限判断。
|
|
|
+ * 【返回值】待结金额字符串,无欠款为 '0.00'
|
|
|
+ */
|
|
|
+ public static function getOutstandingDebt($row)
|
|
|
+ {
|
|
|
+ $net = self::getNetBalanceFromRow($row);
|
|
|
+ if (bccomp($net, '0', 2) >= 0) {
|
|
|
+ return '0.00';
|
|
|
+ }
|
|
|
+ return bcmul($net, '-1', 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】把接口返回给前端的金额字段,按 appVersion 转成旧版或新版形态(多端平滑过渡的核心)。
|
|
|
+ * 【调用时机】CustomClass::groupBaseInfo 组装列表/详情后,对每条记录调用一次。
|
|
|
+ * 【行为】
|
|
|
+ * - appVersion >= 3:balance=净额,debtAmount=0,remainDebtAmount 辅助展示;
|
|
|
+ * - appVersion < 3:拆成旧「待结 debtAmount + 余额 balance + remainDebtAmount」,未升级 App 无需改 UI。
|
|
|
+ * 【参数】$row 须含 balance/debtAmount/balanceMerged;$appVersion 可空则自动读请求头
|
|
|
+ * 【返回值】追加 displayBalance 后的同一数组
|
|
|
+ */
|
|
|
+ public static function formatMoneyForClient(array $row, $appVersion = null)
|
|
|
+ {
|
|
|
+ if ($appVersion === null) {
|
|
|
+ $appVersion = self::getClientAppVersion();
|
|
|
+ }
|
|
|
+ $net = self::getNetBalanceFromRow($row);
|
|
|
+ $row['displayBalance'] = $net;
|
|
|
+
|
|
|
+ if ($appVersion >= self::APP_VERSION_NET_BALANCE) {
|
|
|
+ $row['balance'] = $net;
|
|
|
+ $row['debtAmount'] = '0.00';
|
|
|
+ $remain = bccomp($net, '0', 2) < 0 ? bcmul($net, '-1', 2) : '0.00';
|
|
|
+ if (bccomp($net, '0', 2) > 0) {
|
|
|
+ $remain = bcsub('0', $net, 2);
|
|
|
+ }
|
|
|
+ $row['remainDebtAmount'] = $remain;
|
|
|
+ return $row;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bccomp($net, '0', 2) < 0) {
|
|
|
+ $row['debtAmount'] = bcmul($net, '-1', 2);
|
|
|
+ $row['balance'] = '0.00';
|
|
|
+ $row['remainDebtAmount'] = $row['debtAmount'];
|
|
|
+ } else {
|
|
|
+ $row['debtAmount'] = '0.00';
|
|
|
+ $row['balance'] = $net;
|
|
|
+ $row['remainDebtAmount'] = bccomp($net, '0', 2) > 0 ? bcsub('0', $net, 2) : '0.00';
|
|
|
+ }
|
|
|
+ return $row;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】对 xhGhsCustom(客户)执行一次性「挂账并入余额」:balance -= debtAmount,debtAmount 置 0,打标 balanceMerged。
|
|
|
+ * 【调用时机】一般由 ensureCustomMoneyReady 调用;不要绕过 ensure 直接改 balance。
|
|
|
+ * 【参数】$custom 客户 AR;$writeBalanceChange true 时写一条「账户合并」余额变动(仅首次)
|
|
|
+ * 【返回值】合并后的客户对象
|
|
|
+ */
|
|
|
+ public static function mergeCustomDebtIntoBalanceIfNeeded($custom, $writeBalanceChange = true)
|
|
|
+ {
|
|
|
+ return self::mergeRowDebtIntoBalance($custom, 'custom', $writeBalanceChange);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】对 xhGhs(花店↔供货商关系行)执行与上相同的挂账并入余额。
|
|
|
+ * 【调用时机】ensureCustomMoneyReady 会顺带处理 custom.ghsId;hd 采购-only 场景用 ensureGhsMoneyReady。
|
|
|
+ * 【说明】充值等场景要求 custom 与 ghs 两边净额一致,故成对合并。
|
|
|
+ */
|
|
|
+ public static function mergeGhsDebtIntoBalanceIfNeeded($ghs, $writeBalanceChange = true)
|
|
|
+ {
|
|
|
+ return self::mergeRowDebtIntoBalance($ghs, 'ghs', $writeBalanceChange);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】真正写库的单行合并实现(客户或供货商一行)。
|
|
|
+ * 【说明】已合并则只清理残留 debtAmount;无挂账则只打标;有挂账则改 balance 并可选记流水。
|
|
|
+ * 【注意】须在 getLockById 之后、业务改金额之前调用。
|
|
|
+ */
|
|
|
+ protected static function mergeRowDebtIntoBalance($row, $type, $writeBalanceChange)
|
|
|
+ {
|
|
|
+ if (empty($row)) {
|
|
|
+ return $row;
|
|
|
+ }
|
|
|
+ if (self::isMerged($row)) {
|
|
|
+ if (bccomp($row->debtAmount ?? '0', '0', 2) != 0) {
|
|
|
+ $row->debtAmount = '0.00';
|
|
|
+ $row->save(false, ['debtAmount']);
|
|
|
+ }
|
|
|
+ return $row;
|
|
|
+ }
|
|
|
+
|
|
|
+ $debtAmount = $row->debtAmount ?? '0.00';
|
|
|
+ if (bccomp($debtAmount, '0', 2) == 0) {
|
|
|
+ if (self::hasMergeFlagField($row)) {
|
|
|
+ $row->balanceMerged = 1;
|
|
|
+ $row->save(false, ['balanceMerged']);
|
|
|
+ }
|
|
|
+ return $row;
|
|
|
+ }
|
|
|
+
|
|
|
+ $oldBalance = $row->balance ?? '0.00';
|
|
|
+ $newBalance = bcsub($oldBalance, $debtAmount, 2);
|
|
|
+ $row->balance = $newBalance;
|
|
|
+ $row->debtAmount = '0.00';
|
|
|
+ $saveAttrs = ['balance', 'debtAmount'];
|
|
|
+ if (self::hasMergeFlagField($row)) {
|
|
|
+ $row->balanceMerged = 1;
|
|
|
+ $saveAttrs[] = 'balanceMerged';
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($type === 'custom') {
|
|
|
+ $row->isDebt = bccomp($newBalance, '0', 2) < 0 ? CustomClass::IS_DEBT_YES : CustomClass::IS_DEBT_NO;
|
|
|
+ $saveAttrs[] = 'isDebt';
|
|
|
+ $row->save(false, $saveAttrs);
|
|
|
+ } else {
|
|
|
+ $row->debt = bccomp($newBalance, '0', 2) < 0 ? 2 : 1;
|
|
|
+ $saveAttrs[] = 'debt';
|
|
|
+ $row->save(false, $saveAttrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($writeBalanceChange) {
|
|
|
+ self::addMergeBalanceChange($row, $type, $debtAmount, $newBalance);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $row;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】首次合并时往「余额变动明细」插一条说明,方便财务对账;不写挂账变动表。
|
|
|
+ * 【流水】capitalType=balanceMerge(81),事项如「账户合并:原挂账 X 元并入余额」。
|
|
|
+ */
|
|
|
+ protected static function addMergeBalanceChange($row, $type, $mergedDebt, $newBalance)
|
|
|
+ {
|
|
|
+ $capitalType = dict::getDict('capitalType', 'balanceMerge', 'id');
|
|
|
+ $event = '账户合并:原挂账 ' . floatval($mergedDebt) . ' 元并入余额';
|
|
|
+ if ($type === 'custom') {
|
|
|
+ $cbData = [
|
|
|
+ 'customId' => $row->id ?? 0,
|
|
|
+ 'customName' => $row->name ?? '',
|
|
|
+ 'relateId' => 0,
|
|
|
+ 'onlinePay' => 0,
|
|
|
+ 'ptStyle' => dict::getDict('ptStyle', 'ghs'),
|
|
|
+ 'capitalType' => $capitalType,
|
|
|
+ 'amount' => $mergedDebt,
|
|
|
+ 'balance' => $newBalance,
|
|
|
+ 'staffId' => 0,
|
|
|
+ 'staffName' => '',
|
|
|
+ 'io' => 0,
|
|
|
+ 'side' => 0,
|
|
|
+ 'payWay' => 0,
|
|
|
+ 'fromType' => dict::getDict('fromType', 'shop'),
|
|
|
+ 'event' => $event,
|
|
|
+ 'mainId' => $row->ownMainId ?? ($row->mainId ?? 0),
|
|
|
+ 'shopId' => $row->ownShopId ?? ($row->shopId ?? 0),
|
|
|
+ 'sjId' => $row->sjId ?? 0,
|
|
|
+ 'remark' => '系统自动合并挂账与余额',
|
|
|
+ ];
|
|
|
+ CustomBalanceChangeClass::add($cbData, true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $gbData = [
|
|
|
+ 'ghsId' => $row->id ?? 0,
|
|
|
+ 'relateId' => 0,
|
|
|
+ 'ptStyle' => dict::getDict('ptStyle', 'ghs'),
|
|
|
+ 'capitalType' => $capitalType,
|
|
|
+ 'amount' => $mergedDebt,
|
|
|
+ 'balance' => $newBalance,
|
|
|
+ 'io' => 0,
|
|
|
+ 'side' => 0,
|
|
|
+ 'onlinePay' => 0,
|
|
|
+ 'payWay' => 0,
|
|
|
+ 'fromType' => dict::getDict('fromType', 'shop'),
|
|
|
+ 'event' => $event,
|
|
|
+ 'mainId' => $row->ownMainId ?? ($row->mainId ?? 0),
|
|
|
+ 'shopId' => $row->ownShopId ?? ($row->shopId ?? 0),
|
|
|
+ 'sjId' => $row->sjId ?? 0,
|
|
|
+ 'remark' => '系统自动合并挂账与余额',
|
|
|
+ ];
|
|
|
+ if (class_exists('\bizHd\ghs\classes\GhsBalanceChangeClass')) {
|
|
|
+ \bizHd\ghs\classes\GhsBalanceChangeClass::add($gbData, true);
|
|
|
+ } else {
|
|
|
+ GhsBalanceChangeClass::add($gbData, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】客户侧金额业务的前置入口(最重要,绝大多数写操作应先调此方法)。
|
|
|
+ * 【做什么】行锁客户 → 未合并则合并客户 → 再合并其 ghsId 对应 xhGhs 行。
|
|
|
+ * 【调用时机】开单挂账、结账、退款、充值、余额消费结账、清分改客户金额等。
|
|
|
+ * 【参数】$writeMergeChange false 可关闭合并说明流水(极少用);默认 true
|
|
|
+ * 【返回值】已合并、可安全读写 balance 的客户 AR
|
|
|
+ */
|
|
|
+ public static function ensureCustomMoneyReady($custom, $writeMergeChange = true)
|
|
|
+ {
|
|
|
+ if (empty($custom)) {
|
|
|
+ return $custom;
|
|
|
+ }
|
|
|
+ $customId = is_array($custom) ? ($custom['id'] ?? 0) : ($custom->id ?? 0);
|
|
|
+ if (!is_object($custom) || !self::isMerged($custom)) {
|
|
|
+ $custom = CustomClass::getLockById($customId);
|
|
|
+ }
|
|
|
+ if (empty($custom)) {
|
|
|
+ return $custom;
|
|
|
+ }
|
|
|
+ self::mergeCustomDebtIntoBalanceIfNeeded($custom, $writeMergeChange);
|
|
|
+
|
|
|
+ $ghsId = $custom->ghsId ?? 0;
|
|
|
+ if ($ghsId > 0) {
|
|
|
+ $ghs = BizGhsClass::getLockById($ghsId);
|
|
|
+ if (!empty($ghs)) {
|
|
|
+ self::mergeGhsDebtIntoBalanceIfNeeded($ghs, $writeMergeChange);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $custom;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】仅处理 xhGhs 关系行(无 custom 对象时),逻辑同 ensureCustomMoneyReady 的 ghs 部分。
|
|
|
+ * 【调用时机】hd 采购挂账/结账/退款、ghs 采购单改供货商金额、CgRefund 等只拿到 $ghs 的场景。
|
|
|
+ */
|
|
|
+ public static function ensureGhsMoneyReady($ghs, $writeMergeChange = true)
|
|
|
+ {
|
|
|
+ if (empty($ghs)) {
|
|
|
+ return $ghs;
|
|
|
+ }
|
|
|
+ $ghsId = is_array($ghs) ? ($ghs['id'] ?? 0) : ($ghs->id ?? 0);
|
|
|
+ if (!is_object($ghs) || !self::isMerged($ghs)) {
|
|
|
+ $ghs = BizGhsClass::getLockById($ghsId);
|
|
|
+ }
|
|
|
+ if (empty($ghs)) {
|
|
|
+ return $ghs;
|
|
|
+ }
|
|
|
+ return self::mergeGhsDebtIntoBalanceIfNeeded($ghs, $writeMergeChange);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【用途】客户列表分页时,对「仍有 debtAmount 未合并」的单户触发懒合并(读路径补刀)。
|
|
|
+ * 【调用时机】CustomClass::groupBaseInfo 循环里发现 debtAmount>0 且 balanceMerged=0。
|
|
|
+ * 【说明】避免客户长期无交易一直显示旧双字段;合并后会刷新该行再 formatMoneyForClient。
|
|
|
+ * 【参数】$customId 客户主键
|
|
|
+ */
|
|
|
+ public static function mergeCustomRowFromListIfNeeded($customId)
|
|
|
+ {
|
|
|
+ if (empty($customId)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $custom = CustomClass::getLockById($customId);
|
|
|
+ if (!empty($custom)) {
|
|
|
+ self::ensureCustomMoneyReady($custom, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|