redis->executeCommand('GET', [$cacheKey]); if ($cached !== false && $cached !== null && $cached !== '') { $data = json_decode($cached, true); if (is_array($data)) { return $data; } } $countWhere = array_merge($where, ['balance>' => 0]); $count = self::getCount($countWhere); $totalBalance = self::sum($where, 'balance'); $data = [ 'count' => intval($count), 'totalBalance' => bcadd($totalBalance ?: '0', '0', 2), ]; \Yii::$app->redis->executeCommand('SETEX', [$cacheKey, 60, json_encode($data)]); return $data; } public static function getBalanceStatCacheKey($where) { return 'hd_custom_balance_stat:' . md5(json_encode($where, JSON_UNESCAPED_UNICODE)); } /** * 修改客户生日 * @param $customId * @param $shopId * @param $params * @return array [custom, bool] custom表示客户数据,bool表示是否变动了生日 * @throws \Exception */ public static function modifyBirthday($customId, $shopId, $params) { date_default_timezone_set('PRC'); $custom = self::getById($customId, true); if (empty($custom)) { util::fail('没有找到客户'); } if ($custom->shopId != $shopId) { util::fail('不是你的客户'); } $lunar = $params['lunar'] ?? 0; $birthdayMonth = $params['birthdayMonth'] ?? 1; if ($birthdayMonth > 12 || $birthdayMonth < 1) { util::fail('月份必须1到12月'); } $birthdayDate = $params['birthdayDate'] ?? 1; if ($birthdayDate > 31 || $birthdayDate < 1) { util::fail('日期必须1到31号'); } $userId = $custom->userId; $user = UserClass::getById($userId, true); if (empty($user)) { util::fail('客户信息缺失'); } if ($lunar == 1 && $birthdayDate == 31) { util::fail('农历没有31号'); } $isUpdateBirthday = $user->birthdayTime == 0; // 如果 xhUser 中已经设置了生日,则直接取用 if ($user->birthdayTime != 0) { $custom->lunar = $user->lunar; $custom->birthdayDate = $user->birthdayDate; $custom->birthdayMonth = $user->birthdayMonth; $custom->birthday = $user->birthday; $custom->birthdayTime = $user->birthdayTime; $custom->save(); } else { $calendar = new Calendar(); if ($lunar == 1) { try { $result = $calendar->lunar(date("Y"), $birthdayMonth, $birthdayDate); } catch (\InvalidArgumentException $e) { $msg = $e->getMessage(); util::fail($msg); } //农历转阳历,有多处使用,需要同步修改,关键词 change_my_birthday $month = $result['gregorian_month'] ?? 1; $date = $result['gregorian_day'] ?? 1; $birthday = date("Y") . '-' . $month . '-' . $date; } else { $birthday = date("Y") . '-' . $birthdayMonth . '-' . $birthdayDate; } $birthdayTime = strtotime($birthday); if (getenv('YII_ENV') != 'dev' && $user->birthdaySet == 1) { util::fail('客户已填写,不能修改'); } $custom->lunar = $lunar; $custom->birthdayDate = $birthdayDate; $custom->birthdayMonth = $birthdayMonth; $custom->birthday = $birthday; $custom->birthdayTime = $birthdayTime; $custom->save(); $user->lunar = $lunar; $user->birthdayDate = $birthdayDate; $user->birthdayMonth = $birthdayMonth; $user->birthday = $birthday; $user->birthdayTime = $birthdayTime; $user->save(); } // 同步变更生日赠礼记录表(xhBirthdayGift)中的数据(只更新当年年份的,往年的不去修改) $birthdayGift = BirthdayGiftClass::getByCondition(['userId'=>$user->id, 'shopId'=>$shopId, 'year'=>date("Y")], true); if (!empty($birthdayGift)) { $lunar = intval($custom->lunar); $birthdayMonth = intval($custom->birthdayMonth); $birthdayDate = intval($custom->birthdayDate); list($giftBirthdayMonth, $giftBirthdayDate) = BirthdayGiftClass::convertBirthdayMonthDay( $lunar, $birthdayMonth, $birthdayDate ); $birthdayGift->lunar = $lunar; $birthdayGift->birthdayMonth = $giftBirthdayMonth; $birthdayGift->birthdayDate = $giftBirthdayDate; $birthdayGift->birthdayDisplay = BirthdayGiftClass::formatBirthdayDisplay($custom, $giftBirthdayMonth, $giftBirthdayDate); $birthdayGift->status = 1; $birthdayGift->save(); } return [$custom, $isUpdateBirthday]; } //用余额支付,客户余额减少 ssh 20250410 public static function payToChangeBalance($order) { $actPrice = $order->actPrice ?? 0; $hdId = $order->hdId ?? 0; $hd = HdClass::getLockById($hdId); $balance = $hd->balance ?? 0; if ($actPrice > $balance) { util::fail('余额不足'); } $remain = bcsub($balance, $actPrice, 2); $hd->balance = $remain; $hd->save(); $hdName = $hd->name; $customId = $order->customId ?? 0; $custom = self::getLockById($customId); $balance = $custom->balance ?? 0; if ($actPrice > $balance) { util::fail('余额不足哦'); } $remain = bcsub($balance, $actPrice, 2); $custom->balance = $remain; $custom->save(); $customName = $custom->name; if ($custom->balance != $hd->balance) { util::fail('余额有问题哦'); } $relateId = $order->id ?? 0; //0 花店操作 1 客户操作 $payWay = $order->payWay; $side = 1; $shopId = $order->shopId; $mainId = $order->mainId; $staffId = $order->shopAdminId; $staffName = $order->shopAdminName; $orderSn = $order->orderSn; $event = '买花,单号:' . $orderSn; $capitalType = dict::getDict('capitalType', 'xhOrder', 'id'); $change = [ 'hdId' => $hdId, 'hdName' => $hdName, 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'onlinePay' => 1, 'capitalType' => $capitalType, 'amount' => $actPrice, 'balance' => $hd->balance, 'io' => 0, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $shopId, 'mainId' => $mainId, 'remark' => '', ]; BalanceChangeClass::add($change, true); $order->balance = $actPrice; $order->save(); // 多处需要同步修改 balance_pay_give if ($hd->balancePay >= $actPrice) { $payAmount = $actPrice; $mustUseGiveAmount = 0; } else { $payAmount = $hd->balancePay; $mustUseGiveAmount = bcsub($actPrice, $payAmount, 2); if ($mustUseGiveAmount > $hd->balanceGive) { util::fail('赠送余额异常,不够扣'); } } /************************* 使用充值余额 *****************************/ $customBalancePay = bcsub($custom->balancePay, $payAmount, 2); $custom->balancePay = $customBalancePay; $custom->save(); $hdBalancePay = bcsub($hd->balancePay, $payAmount, 2); $hd->balancePay = $hdBalancePay; $hd->save(); $payChange = [ 'hdId' => $hdId, 'hdName' => $hdName, 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'onlinePay' => 1, 'capitalType' => $capitalType, 'amount' => $payAmount, 'balance' => $customBalancePay, 'io' => 0, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $shopId, 'mainId' => $mainId, 'remark' => '', ]; BalancePayChangeClass::add($payChange, true); $order->payBalance = $payAmount; $order->save(); /***************************** 使用赠送余额 ******************************/ //应用掉的赠送金额 if ($mustUseGiveAmount > 0) { $customBalanceGive = bcsub($custom->balanceGive, $mustUseGiveAmount, 2); if ($customBalanceGive < 0) { util::fail('余额异常哈!请联系管理员,编号' . $hdId . ' ' . $custom->balanceGive); } $custom->balanceGive = $customBalanceGive; $custom->save(); $hdBalanceGive = bcsub($hd->balanceGive, $mustUseGiveAmount, 2); $hd->balanceGive = $hdBalanceGive; $hd->save(); if (floatval($customBalanceGive) != floatval($hdBalanceGive)) { util::fail('余额有问题呢!请联系管理员,编号' . $hdId); } $giveChange = [ 'hdId' => $hdId, 'hdName' => $hdName, 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'onlinePay' => 1, 'capitalType' => $capitalType, 'amount' => $mustUseGiveAmount, 'balance' => $customBalanceGive, 'io' => 0, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $shopId, 'mainId' => $mainId, 'remark' => '', ]; BalanceGiveChangeClass::add($giveChange, true); $order->giveBalance = $mustUseGiveAmount; $order->save(); } $addBalance = bcadd($hd->balancePay, $hd->balanceGive, 2); if (floatval($addBalance) != floatval($custom->balance)) { util::fail('账户余额有问题!请检查,编号' . $customId); } } //会员等级变化,包括手动修改、自动降会员和充值升会员等 ssh 20250324 public static function levelChange($oldLevel, $newLevel, $shop, $custom, $hd, $params) { $adminId = $params['adminId']; $staffId = $params['staffId'] ?? 0; $staffName = $params['staffName'] ?? ''; $style = $params['style'] ?? 0; $target = $params['target'] ?? []; $rechargeAmount = $params['rechargeAmount'] ?? 0; $targetId = $target->id ?? 0; $mainId = $shop->mainId; $shopId = $shop->id; $levelData = MemberLevelClass::getAllByCondition(['mainId' => $mainId], null, '*', 'level'); $current = $levelData[$newLevel] ?? []; $before = $levelData[$oldLevel] ?? []; $beforeName = $before['name'] ?? '普通客户'; $discount = $current['discount'] ? $current['discount'] / 10 : 1; $memberName = $current['name'] ?? ''; $oldGrowth = $custom->growth; $hd->member = $newLevel; $hd->discount = $discount; $hd->memberName = $memberName; $hd->save(); $event = '手动修改'; if ($style == 0) { util::fail('充值不支持升级会员'); } $change = [ 'customId' => $custom->id, 'customName' => $custom->name, 'hdId' => $hd->id, 'hdName' => $hd->name, 'mainId' => $mainId, 'shopId' => $shopId, 'targetId' => $targetId, 'rechargeAmount' => $rechargeAmount, 'days' => 0, 'daysBefore' => '0000-00-00 00:00:00', 'daysAfter' => '0000-00-00 00:00:00', 'beforeLevel' => $oldLevel, 'afterLevel' => $newLevel, 'beforeName' => $beforeName, 'afterName' => $memberName, 'style' => $style, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, ]; MemberChangeClass::add($change); $custom->member = $newLevel; $custom->discount = $discount; $custom->memberName = $memberName; //成长值变更 $growth = $current['amount'] ?? 0; $custom->growth = $growth; $custom->save(); //成长值变动记录 $growthData = [ 'shopId' => $shopId, 'userId' => $custom->userId, 'userName' => $custom->name, 'event' => "管理员变更{$memberName}级,成长值从{$oldGrowth}变更为{$growth}", 'relateId' => 0, 'relateType' => 0, 'num' => $custom->growth - $oldGrowth, 'growth' => $growth, //'operatorId' => $adminId, 'staffId' => $staffId, //操作人(员工id) 'staffName' => $staffName, //员工名称 'createTime' => date('Y-m-d H:i:s') ]; UserGrowthClass::add($growthData); } //客户采购欠款金额增加,如果有余额时,要进行欠款销账 ssh 20250715 //这边开欠款单时,如果还有余额,会产生结账动作,结账动作也可以用赠送余额来结账,所以要分出赠送余额的占比 //结账消余额,不是下单时消余额,不涉及售后时余额回滚问题,所以不需要设置 xhOrder 的 giveBalance payBalance balance等值 public static function skCgDebtAmountAdd($custom, $order) { $clearAmount = 0; $amount = $order->remainDebtPrice ?? 0; $staffId = $order->shopAdminId ?? 0; $staffName = $order->shopAdminName ?? ''; $orderId = $order->id; $orderSn = $order->orderSn ?? ''; $shopId = $order->shopId; $settleNo = ''; $settleId = 0; $settleAmount = 0; $hdId = $custom->hdId ?? 0; $hd = HdClass::getLockById($hdId); if (empty($hd)) { util::fail('花店信息缺失,编号6325'); } $hdName = $hd->name ?? ''; //结账时用了多少赠送的余额 $settleUseGiveAmount = 0; if ($custom->balance > 0) { if ($amount > $custom->balance) { $clearAmount = $custom->balance; $settleUseGiveAmount = $custom->balanceGive; } else { util::fail('余额充足,请用余额支付'); } } if ($clearAmount > 0) { $shop = ShopClass::getById($shopId, true); $needClearIdMap[] = ['orderId' => $orderId, 'clearAmount' => $clearAmount, 'orderSn' => $orderSn]; $setParams = ['staffId' => $staffId, 'staffName' => $staffName]; $set = SettleClass::addSettle($needClearIdMap, $shop, $custom, $hd, $setParams); $ret = SettleClass::clearSettle($set); $settleAmount = $ret['totalClearAmount'] ?? 0; $settleNo = $set->orderSn ?? ''; $settleId = $set->id; $newSettle = $ret['settle']; //结账时用了多少赠送的余额 $newSettle->giveBalance = bcadd($newSettle->giveBalance, $settleUseGiveAmount, 2); $newSettle->save(); } /************************ 总余额 *********************************/ $customBalance = bcsub($custom->balance, $amount, 2); $custom->balance = $customBalance; if ($custom->balance < 0) { $custom->isDebt = 1; } $custom->save(); $hdBalance = bcsub($hd->balance, $amount, 2); $hd->balance = $hdBalance; if ($hd->balance < 0) { $hd->debt = 2; } $hd->save(); if (floatval($customBalance) != floatval($hdBalance)) { util::fail('余额有错,请联系管理员,编号' . $hdId); } $relateId = $order->id ?? 0; $customId = $custom->id ?? 0; $orderSn = $order->orderSn ?? ''; $capitalType = dict::getDict('capitalType', 'xhOrder', 'id'); $event = '购买花材,单号:' . $orderSn; $customName = $custom->name ?? ''; $onlinePay = 1; $side = 0; $payWay = 0; $change = [ 'hdId' => $hdId, 'hdName' => $hdName, 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'onlinePay' => $onlinePay, 'capitalType' => $capitalType, 'amount' => $amount, 'balance' => $customBalance, 'io' => 0, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $shopId, 'mainId' => $order->mainId, 'settleId' => $settleId, 'settleNo' => $settleNo, 'settleAmount' => $settleAmount, 'remark' => '', ]; BalanceChangeClass::add($change, true); $mustUseGiveAmount = 0; // 多处需要同步修改 balance_pay_give if ($hd->balancePay >= $amount) { util::fail('请用余额支付哈'); } else { // 如果【充值金额】小于订单欠款金额,则用全部【充值金额】消单,同时必须用上【赠送金额】,够抵,则【充值金额】还是正数,不够抵,则是负数 if ($hd->balanceGive <= 0) { //没有【赠送金额】,则用【充值金额】来消全部的欠款金额,不够则变成负数 $payAmount = $amount; } else { //这个是【总余额】,包括【充值金额】和【赠送金额】 $currentAdd = bcadd($hd->balancePay, $hd->balanceGive, 2); if ($currentAdd >= $amount) { util::fail('请使用余额支付哦'); } else { //【总余额】不够抵欠款金额,先用掉全部的【赠送金额】,其他的全部算在【充值金额】名下,因为充值金额也不够扣,会出现负数 $mustUseGiveAmount = $hd->balanceGive; $payAmount = bcsub($amount, $mustUseGiveAmount, 2); } } } /************************* 充值余额 *****************************/ $customBalancePay = bcsub($custom->balancePay, $payAmount, 2); $custom->balancePay = $customBalancePay; $custom->save(); $hdBalancePay = bcsub($hd->balancePay, $payAmount, 2); $hd->balancePay = $hdBalancePay; $hd->save(); if (floatval($customBalancePay) != floatval($hdBalancePay)) { util::fail('余额有错呢,请联系管理员,编号' . $hdId); } $relateId = $order->id ?? 0; $customId = $custom->id ?? 0; $orderSn = $order->orderSn ?? ''; $capitalType = dict::getDict('capitalType', 'xhOrder', 'id'); $event = '购买花材,单号:' . $orderSn; $customName = $custom->name ?? ''; $onlinePay = 1; $side = 0; $payWay = 0; $payChange = [ 'hdId' => $hdId, 'hdName' => $hdName, 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'onlinePay' => $onlinePay, 'capitalType' => $capitalType, 'amount' => $payAmount, 'balance' => $customBalancePay, 'io' => 0, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $shopId, 'mainId' => $order->mainId, 'settleId' => $settleId, 'settleNo' => $settleNo, 'settleAmount' => $settleAmount, 'remark' => '', ]; BalancePayChangeClass::add($payChange, true); /***************************** 赠送余额 ******************************/ //应用掉的赠送金额 if ($mustUseGiveAmount > 0) { $customBalanceGive = bcsub($custom->balanceGive, $mustUseGiveAmount, 2); if ($customBalanceGive < 0) { util::fail('余额异常哈,请联系管理员,编号' . $hdId . ' ' . $custom->balanceGive); } $custom->balanceGive = $customBalanceGive; $custom->save(); $hdBalanceGive = bcsub($hd->balanceGive, $mustUseGiveAmount, 2); $hd->balanceGive = $hdBalanceGive; $hd->save(); if (floatval($customBalanceGive) != floatval($hdBalanceGive)) { util::fail('余额有问题呢,请联系管理员,编号' . $hdId); } $relateId = $order->id ?? 0; $customId = $custom->id ?? 0; $orderSn = $order->orderSn ?? ''; $capitalType = dict::getDict('capitalType', 'xhOrder', 'id'); $event = '购买花材,单号:' . $orderSn; $customName = $custom->name ?? ''; $onlinePay = 1; $side = 0; $payWay = 0; $giveChange = [ 'hdId' => $hdId, 'hdName' => $hdName, 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'onlinePay' => $onlinePay, 'capitalType' => $capitalType, 'amount' => $mustUseGiveAmount, 'balance' => $customBalanceGive, 'io' => 0, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $shopId, 'mainId' => $order->mainId, 'settleId' => $settleId, 'settleNo' => $settleNo, 'settleAmount' => $settleAmount, 'remark' => '', ]; BalanceGiveChangeClass::add($giveChange, true); } $addBalance = bcadd($hd->balancePay, $hd->balanceGive, 2); if (floatval($addBalance) != floatval($custom->balance)) { util::fail('账户余额有问题,请检查,编号' . $customId); } } /** * 隔天/无原单售后返充余额(不依赖销售单 balanceTk 拆分) * 用途:成功页「返充到客户余额」;无原单时 $order 可为空。 * 入账规则:实付退回算本金,同步 +balance 与 +balancePay,并写总流水 + 充值余额流水; * 不进赠送余额(赠送仅来自充值活动),以保持 balance ≈ balancePay + balanceGive。 */ public static function nextDayRefundReturnBalance($custom, $amount, $refund, $order = null) { $amount = bcadd((string)$amount, '0', 2); if (bccomp($amount, '0', 2) <= 0) { util::fail('返充金额必须大于0'); } $hdId = $custom->hdId ?? 0; $hd = HdClass::getLockById($hdId); if (empty($hd)) { util::fail('花店信息缺失,编号5363'); } // —— 总余额 —— $customBalance = bcadd((string)($custom->balance ?? '0'), $amount, 2); $custom->balance = $customBalance; $custom->isDebt = bccomp($customBalance, '0', 2) < 0 ? 1 : 0; $custom->save(); $hdBalance = bcadd((string)($hd->balance ?? '0'), $amount, 2); $hd->balance = $hdBalance; if (bccomp($hdBalance, '0', 2) < 0) { $hd->debt = 2; } $hd->save(); if (floatval($customBalance) !== floatval($hdBalance)) { util::fail('余额错误,请联系管理员,编号' . $hdId); } // —— 充值本金:线下/无原单返充视为实付退回,进 balancePay(不进赠送)—— $customBalancePay = bcadd((string)($custom->balancePay ?? '0'), $amount, 2); $custom->balancePay = $customBalancePay; $custom->save(false, ['balancePay']); $hdBalancePay = bcadd((string)($hd->balancePay ?? '0'), $amount, 2); $hd->balancePay = $hdBalancePay; $hd->save(false, ['balancePay']); if (floatval($customBalancePay) !== floatval($hdBalancePay)) { util::fail('充值余额错误,请联系管理员,编号' . $hdId); } $refundSn = $refund->refundSn ?? ''; $orderId = intval($refund->orderId ?? 0); $saleOrderSn = !empty($order) ? ($order->orderSn ?? '') : ''; if ($saleOrderSn === '' && $orderId > 0) { $saleOrderSn = $refund->orderSn ?? ''; } // 无原单流水只挂售后单号,避免「原单:」空串误导 $event = ($orderId > 0 && $saleOrderSn !== '') ? "跨天售后返充余额,售后单:{$refundSn},原单:{$saleOrderSn}" : "无原单退款返充余额,售后单:{$refundSn}"; $capitalType = dict::getDict('capitalType', 'hdRefund', 'id'); $customId = intval($custom->id ?? 0); $customName = $custom->name ?? ''; $staffId = intval($refund->shopAdminId ?? 0); $staffName = $refund->shopAdminName ?? ''; $shopId = intval($refund->shopId ?? 0); $mainId = intval($refund->mainId ?? 0); $changeBase = [ 'customId' => $customId, 'customName' => $customName, 'relateId' => $refund->id ?? 0, 'relateSn' => $refundSn, 'onlinePay' => 1, 'capitalType' => $capitalType, 'io' => 1, 'side' => 0, 'payWay' => 2, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $shopId, 'mainId' => $mainId, 'refundOrderId' => $orderId, 'refundOrderSn' => $saleOrderSn, 'remark' => '', ]; // 总余额流水(balanceChange 页) BalanceChangeClass::add(array_merge($changeBase, [ 'amount' => $amount, 'balance' => $customBalance, ]), true); // 充值本金流水(balancePayChange / 商城余额变动页) BalancePayChangeClass::add(array_merge($changeBase, [ 'amount' => $amount, 'balance' => $hdBalancePay, ]), true); // 返充入账后:优先结当前售后原单挂账,剩余再 FIFO(不再二次扣余额) $balanceBefore = bcsub($customBalance, $amount, 2); self::fifoClearAfterRefundCredit($custom, $hd, $refund, $amount, $balanceBefore, $order); $custom = CustomClass::getById($customId, true); return bcadd((string)($custom->balance ?? $customBalance), '0', 2); } //欠款和余额支付的售后 ssh 20250803 public static function skRefundDebtAmountReduce($custom, $amount, $refund, $payWay) { $hdId = $custom->hdId ?? 0; $hd = HdClass::getLockById($hdId); if (empty($hd)) { util::fail('花店信息缺失,编号5362'); } // 入账前净余额,供结账单 payWay 判定 $balanceBefore = bcadd((string)($custom->balance ?? '0'), '0', 2); // 原始支付方式:后面流水会改写成余额付,FIFO 判定要用原值 $originPayWay = intval($payWay); $customBalance = bcadd($custom->balance, $amount, 2); $custom->balance = $customBalance; if ($custom->balance < 0) { $custom->isDebt = 1; } $custom->save(); $hdBalance = bcadd($hd->balance, $amount, 2); $hd->balance = $hdBalance; if ($hd->balance < 0) { $hd->debt = 2; } $hd->save(); if (floatval($customBalance) !== floatval($hdBalance)) { util::fail('余额错误,请联系管理员,编号' . $hdId); } $relateId = $refund->id ?? 0; $customId = $custom->id ?? 0; $refundSn = $refund->refundSn ?? ''; $orderSn = $refund->orderSn ?? ''; $orderId = $refund->orderId ?? 0; $order = OrderClass::getLockById($orderId); if (empty($order)) { util::fail('没有找到订单哈!'); } // 多处需要同步修改 balance_pay_give $mustUseGiveAmount = 0; $buyAmount = 0; if ($payWay == 3) { //如果是欠款单售后,没有退款到赠送余额的情况 $buyAmount = $amount; } elseif ($payWay == 2) { //这个是总消费余额中充值余额部分 $payBalance = $order->payBalance ?? 0; //这个是累计已经退还的余额 $balanceTk = $order->balanceTk ?? 0; $currentBalanceTk = bcadd($balanceTk, $amount, 2); //当前累计退还的余额如果超过了充值余额的话,说明要退还赠送的余额了 if ($currentBalanceTk > $payBalance) { $pastAmount = bcsub($currentBalanceTk, $payBalance, 2); if ($pastAmount > $amount) { $mustUseGiveAmount = $amount; } else { $buyAmount = bcsub($amount, $pastAmount, 2); $mustUseGiveAmount = bcsub($amount, $buyAmount, 2); } } else { //累计退还的余额,还没超过充值余额,还是退充值余额部分 $buyAmount = $amount; } } else { util::fail('无效的支付方式!编号69880'); } $staffId = $refund->shopAdminId ?? 0; $staffName = $refund->shopAdminName ?? ''; $capitalType = dict::getDict('capitalType', 'hdRefund', 'id'); $event = "订单售后 " . $refundSn; $customName = $custom->name ?? ''; $onlinePay = 1; $side = 0; $payWay = 2;//余额支付 $change = [ 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'relateSn' => $refundSn, 'onlinePay' => $onlinePay, 'capitalType' => $capitalType, 'amount' => $amount, 'balance' => $customBalance, 'io' => 1, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $refund->shopId, 'mainId' => $refund->mainId, 'refundOrderId' => $orderId, 'refundOrderSn' => $orderSn, 'remark' => '', ]; BalanceChangeClass::add($change, true); $order->balanceTk = bcadd($order->balanceTk, $amount, 2); if ($order->balanceTk < 0) { util::fail('不能小于0,编号88672'); } $order->save(); if ($buyAmount > 0) { $customBalancePay = bcadd($custom->balancePay, $buyAmount, 2); $custom->balancePay = $customBalancePay; $custom->save(); $hdBalancePay = bcadd($hd->balancePay, $buyAmount, 2); $hd->balancePay = $hdBalancePay; $hd->save(); $payChange = [ 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'relateSn' => $refundSn, 'onlinePay' => $onlinePay, 'capitalType' => $capitalType, 'amount' => $buyAmount, 'balance' => $hdBalancePay, 'io' => 1, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $refund->shopId, 'mainId' => $refund->mainId, 'refundOrderId' => $orderId, 'refundOrderSn' => $orderSn, 'remark' => '', ]; BalancePayChangeClass::add($payChange, true); $order->payBalanceTk = bcadd($order->payBalanceTk, $buyAmount, 2); if ($order->payBalanceTk < 0) { util::fail('不能小于0,编号88673'); } $order->save(); } if ($mustUseGiveAmount > 0) { $customBalanceGive = bcadd($custom->balanceGive, $mustUseGiveAmount, 2); $custom->balanceGive = $customBalanceGive; $custom->save(); $hdBalanceGive = bcadd($hd->balanceGive, $mustUseGiveAmount, 2); $hd->balanceGive = $hdBalanceGive; $hd->save(); $giveChange = [ 'customId' => $customId, 'customName' => $customName, 'relateId' => $relateId, 'relateSn' => $refundSn, 'onlinePay' => $onlinePay, 'capitalType' => $capitalType, 'amount' => $mustUseGiveAmount, 'balance' => $hdBalanceGive, 'io' => 1, 'side' => $side, 'payWay' => $payWay, 'event' => $event, 'staffId' => $staffId, 'staffName' => $staffName, 'shopId' => $refund->shopId, 'mainId' => $refund->mainId, 'refundOrderId' => $orderId, 'refundOrderSn' => $orderSn, 'remark' => '', ]; BalanceGiveChangeClass::add($giveChange, true); $order->giveBalanceTk = bcadd($order->giveBalanceTk, $mustUseGiveAmount, 2); if ($order->giveBalanceTk < 0) { util::fail('不能小于0,编号88675'); } $order->save(); } // 当天挂账售后已先减 remainDebtPrice,再 FIFO 优先本单会重复销账;仅隔天/返充类或余额付原路才消欠 $sameDay = intval($refund->sameDay ?? 1); $debtPay = intval(dict::getDict('payWay', 'debtPay')); $skipFifo = ($originPayWay === $debtPay && $sameDay === 1); if (!$skipFifo) { self::fifoClearAfterRefundCredit($custom, $hd, $refund, $amount, $balanceBefore, $order); } } /** * 售后返充/原路退余额后销挂账:优先当前售后原单 remainDebtPrice(可部分结),再按 id 升序 FIFO。 * 调用时机:余额已入账;本方法只用本次金额作资金池,不再扣 balance。 * * @param object $custom 客户(已加锁) * @param object $hd 花店账户 * @param object $refund 售后单 * @param string|float $amount 本次入账金额 * @param string|float $balanceBefore 入账前净余额 * @param object|null $order 售后关联销售单 */ protected static function fifoClearAfterRefundCredit($custom, $hd, $refund, $amount, $balanceBefore, $order = null) { $pool = bcadd((string)$amount, '0', 2); if (bccomp($pool, '0', 2) <= 0) { return; } $customId = intval($custom->id ?? 0); if ($customId <= 0) { return; } $preferOrderId = !empty($order) ? intval($order->id ?? 0) : intval($refund->orderId ?? 0); $plan = self::planFifoClearByPool($customId, $pool, $preferOrderId); if (empty($plan)) { return; } $shopId = intval($refund->shopId ?? ($custom->shopId ?? 0)); $shop = $shopId > 0 ? ShopClass::getById($shopId, true) : null; if (empty($shop)) { // 无门店无法建结账单,跳过销账(余额已入账,不阻断售后) return; } $settleParams = [ 'staffId' => intval($refund->shopAdminId ?? 0), 'staffName' => (string)($refund->shopAdminName ?? ''), ]; $set = SettleClass::addSettle($plan, $shop, $custom, $hd, $settleParams); if (!empty($set)) { SettleClass::clearSettle($set); } // 刷新客户欠款标记(有未结挂账单则为欠款户) $leftDebt = OrderClass::getAllByCondition(['customId' => $customId, 'debt' => 1], null, 'id', null, true); $customFresh = self::getLockById($customId); if (!empty($customFresh)) { $customFresh->isDebt = !empty($leftDebt) ? 1 : 0; $customFresh->save(false, ['isDebt']); } } /** * 销挂账分配:可选优先本单,剩余按订单 id 升序 FIFO。 * prefer 路径只用 remainDebtPrice,不用 actPrice 兜底,避免脏数据多销。 * * @param int $customId * @param string|float $poolAmount * @param int $preferOrderId * @return array */ protected static function planFifoClearByPool($customId, $poolAmount, $preferOrderId = 0) { $pool = bcadd((string)$poolAmount, '0', 2); if (bccomp($pool, '0', 2) <= 0) { return []; } $orderList = OrderClass::getAllByCondition(['customId' => $customId, 'debt' => 1], 'id asc', '*', null, true); if (empty($orderList)) { return []; } $plan = []; $used = '0.00'; $preferOrderId = intval($preferOrderId); // 1)售后原单仍挂账:先结本单(不够则部分结) if ($preferOrderId > 0) { foreach ($orderList as $ord) { if (intval($ord->id ?? 0) !== $preferOrderId) { continue; } $remain = bcadd((string)($ord->remainDebtPrice ?? '0'), '0', 2); if (bccomp($remain, '0', 2) <= 0) { break; } $clearAmount = bccomp($remain, $pool, 2) <= 0 ? $remain : $pool; $plan[] = [ 'orderId' => $preferOrderId, 'clearAmount' => $clearAmount, 'orderSn' => $ord->orderSn ?? '', ]; $used = bcadd($used, $clearAmount, 2); break; } } // 2)剩余金额 FIFO(跳过已优先处理的本单) foreach ($orderList as $ord) { $left = bcsub($pool, $used, 2); if (bccomp($left, '0', 2) <= 0) { break; } $oid = intval($ord->id ?? 0); if ($preferOrderId > 0 && $oid === $preferOrderId) { continue; } $remain = bcadd((string)($ord->remainDebtPrice ?? '0'), '0', 2); if (bccomp($remain, '0', 2) <= 0) { continue; } $clearAmount = bccomp($remain, $left, 2) <= 0 ? $remain : $left; $plan[] = [ 'orderId' => $oid, 'clearAmount' => $clearAmount, 'orderSn' => $ord->orderSn ?? '', ]; $used = bcadd($used, $clearAmount, 2); } return $plan; } //添加客户 ssh 2021.2.28 public static function addCustom($data) { return self::add($data, true); } //创建或获取客户信息 ssh 2021.3.19 /** * 建立用户与花店的 xhCustom / hd 关系(花店增加客户) * @param object $shop 门店 * @param object $user 商城用户 * @param int $inviterCustomId 分享邀请人 xhCustom.id,仅新建客户时尝试拉新绑定 * @return array|false custom、hd、isNewCustom、inviteBound */ public static function buildRelation($shop, $user, $inviterCustomId = 0) { if (empty($user)) { return false; } if (empty($shop)) { return false; } if ($shop->ptStyle == 2) { util::fail('有问题,批发店状态'); } $salt = stringUtil::charsShuffleLowerCase(8); $isNewCustom = 0; /** 仅 xhCustom 新建时为 1,供分享拉新绑定(与 hd 新建触发的 $isNewCustom 区分) */ $customCreated = 0; $userId = $user->id; $name = $user->name ?? ''; $mobile = $user->mobile ?? ''; $shopId = $shop->id ?? 0; $mainId = $shop->mainId ?? 0; $hdAvatar = $shop->avatar ?? ''; $sjId = $shop->sjId ?? 0; $sjName = $shop->merchantName ?? ''; $shopName = $shop->shopName ?? ''; $hdMobile = $shop->mobile ?? ''; $hdName = $shopName == '首店' ? $sjName : $sjName . ' ' . $shopName; $hdPy = stringUtil::py($hdName); $custom = self::getByCondition(['shopId' => $shopId, 'userId' => $userId], true); if (empty($custom)) { $py = stringUtil::py($name); $visitTime = date("Y-m-d H:i:s"); $data = [ 'userId' => $userId, 'sjId' => $sjId, 'shopId' => $shopId, 'mainId'=>$mainId, 'name' => $name, 'originName' => $name, 'py' => $py, 'mobile' => $mobile, 'visitTime' => $visitTime, 'salt' => $salt, ]; $birthdaySet = $user->birthdaySet ?? 0; if ($birthdaySet == 1) { //如果用户自己设置过了生日,则生日给传过去 $lunar = $user->lunar ?? 0; $birthday = $user->birthday ?? ''; $birthdayTime = $user->birthdayTime ?? 0; $birthdayMonth = $user->birthdayMonth ?? 0; $birthdayDate = $user->birthdayDate ?? 0; //如果当年的生日时间没有更新,则需要给更新 $year = date("Y", strtotime($birthday)); $thisYear = date("Y"); if ($year != $thisYear) { if ($lunar == 1) { try { //农历转阳历,有多处使用,需要同步修改,关键词change_my_birthday $calendar = new Calendar(); $result = $calendar->lunar(date("Y"), $birthdayMonth, $birthdayDate); $month = $result['gregorian_month'] ?? 1; $date = $result['gregorian_day'] ?? 1; $birthday = date("Y") . '-' . $month . '-' . $date; $user->birthday = $birthday; $user->save(); CustomClass::updateByCondition(['userId' => $userId], ['birthday' => $birthday]); } catch (\InvalidArgumentException $e) { $msg = $e->getMessage(); noticeUtil::push("同步生日出错了,需要检查确认,userId:" . $userId . ' 月份:' . $birthdayMonth . ' 日期:' . $birthdayDate . ' ' . $msg, '15280215347'); $birthday = '0000-00-00 00:00:00'; } } else { $birthday = date("Y") . '-' . $birthdayMonth . '-' . $birthdayDate; $user->birthday = $birthday; $user->save(); CustomClass::updateByCondition(['userId' => $userId], ['birthday' => $birthday]); } } $data['birthday'] = $birthday; $data['birthdayTime'] = $birthdayTime; $data['birthdayMonth'] = $birthdayMonth; $data['birthdayDate'] = $birthdayDate; $data['lunar'] = $lunar; } // 处理并发插入:尝试插入,如果遇到唯一键冲突则重新查询 try { $custom = self::addCustom($data); $isNewCustom = 1; $customCreated = 1; } catch (\yii\db\IntegrityException $e) { // 唯一键冲突,说明其他并发请求已经插入,重新查询 if (strpos($e->getMessage(), 'Duplicate entry') !== false) { $custom = self::getByCondition(['shopId' => $shopId, 'userId' => $userId], true); if (empty($custom)) { // 如果仍然查不到,则抛出异常 throw $e; } $isNewCustom = 0; $customCreated = 0; } else { throw $e; } } } $customId = $custom->id ?? 0; $hd = HdClass::getByCondition(['shopId' => $shopId, 'userId' => $userId], true); if (empty($hd)) { $hdData = [ 'shopId' => $shopId, 'userId' => $userId, 'name' => $hdName, 'sjId' => $sjId, 'mainId' => $mainId, 'customId' => $customId, 'avatar' => $hdAvatar, 'py' => $hdPy, 'mobile' => $hdMobile, 'province' => $shop->province ?? '', 'city' => $shop->city ?? '', 'dist' => $shop->dist ?? '', 'address' => $shop->address ?? '', 'floor' => $shop->floor ?? '', 'fullAddress' => $shop->fullAddress ?? '', 'showAddress' => $shop->showAddress ?? '', 'lat' => $shop->lat ?? '', 'long' => $shop->long ?? '', 'salt' => $salt, ]; // 处理并发插入:尝试插入,如果遇到唯一键冲突则重新查询 try { $hd = HdClass::addData($hdData); } catch (\yii\db\IntegrityException $e) { // 唯一键冲突,说明其他并发请求已经插入,重新查询 if (strpos($e->getMessage(), 'Duplicate entry') !== false) { $hd = HdClass::getByCondition(['shopId' => $shopId, 'userId' => $userId], true); if (empty($hd)) { // 如果仍然查不到,则抛出异常 throw $e; } } else { throw $e; } } $isNewCustom = 1; } $hdId = $hd->id ?? 0; $custom->hdId = $hdId; $custom->save(); // 新建 xhCustom 时:customId 无分销记录则创建,有邀请人则绑定 $inviteBound = 0; $inviterCustomId = (int)$inviterCustomId; try { $inviteBound = DistributionUserClass::bindInviteOnRegister( (int)$shopId, (int)$customId, $inviterCustomId ); } catch (\Throwable $e) { // 分销初始化失败不影响建客主流程 } if ($isNewCustom == 1) { WxMessageClass::newHdCustomInform($shop, $custom); } // 新客/受邀新客红包:仅新建 xhCustom 时触发;受邀优先且不叠加发新客红包 if ($customCreated == 1) { try { $mainId = (int)($shop->mainId ?? 0); $customArr = is_object($custom) ? $custom->attributes : $custom; if ($inviteBound == 1) { HbAutoRuleClass::tryIssueOnRegister($shopId, $mainId, $customArr, HbAutoRule::TYPE_INVITED); } else { HbAutoRuleClass::tryIssueOnRegister($shopId, $mainId, $customArr, HbAutoRule::TYPE_NEW); } } catch (\Throwable $e) { Yii::error('注册自动发红包失败 customId=' . $customId . ' ' . $e->getMessage(), __METHOD__); } } return [ 'custom' => $custom, 'hd' => $hd, 'isNewCustom' => $customCreated, 'inviteBound' => $inviteBound, ]; } //获取客户信息 public static function getCustom($data) { $custom = self::getByCondition($data); if (empty($custom)) { $custom = self::add($data); } return $custom; } //获取客户信息 ssh 2021.2.1 public static function getCustomInfo($id) { $custom = self::getById($id); if (empty($custom)) { return $custom; } $list = self::groupBaseInfo([$custom]); return current($list); } //获取客户信息 ssh 2021.3.19 public static function getCustomByIds($ids) { $list = self::getByIds($ids); $list = self::groupBaseInfo($list); return $list; } public static function valid($info, $shopId) { if (isset($info['shopId']) && $info['shopId'] == $shopId) { return true; } util::fail('客户信息无效'); } //整合基本信息 ssh 2021.1.31 public static function groupBaseInfo($list) { if (empty($list)) { return $list; } foreach ($list as $key => $val) { $shortAvatar = $val['avatar'] ?? ''; $avatar = imgUtil::groupImg($shortAvatar) . "?x-oss-process=image/resize,m_fill,h_800,w_800"; $smallAvatar = imgUtil::groupImg($shortAvatar) . "?x-oss-process=image/resize,m_fill,h_130,w_130"; $bigAvatar = imgUtil::groupImg($shortAvatar) . "?x-oss-process=image/resize,m_fill,h_700,w_700"; $list[$key]['shortAvatar'] = $shortAvatar; $list[$key]['avatar'] = $avatar; $list[$key]['smallAvatar'] = $smallAvatar; $list[$key]['bigAvatar'] = $bigAvatar; //超时未下单 $recentExpend = $val['recentExpend'] ?? ''; $list[$key]['overTimeUnExpend'] = 0; if ($recentExpend == '0000-00-00 00:00:00') { $list[$key]['overTimeUnExpend'] = 10000; } else { $recentExpendTime = strtotime($recentExpend); $seven = bcmul(86400, 7); $sevenTime = bcadd($recentExpendTime, $seven); if ($sevenTime < time()) { $list[$key]['overTimeUnExpend'] = 7; } $month = bcmul(86400, 30); $monthTime = bcadd($recentExpendTime, $month); if ($monthTime < time()) { $list[$key]['overTimeUnExpend'] = 30; } $halfYear = bcmul(86400, 180); $halfYearTime = bcadd($recentExpendTime, $halfYear); if ($halfYearTime < time()) { $list[$key]['overTimeUnExpend'] = 180; } $year = bcmul(86400, 365); $yearTime = bcmul($recentExpendTime, $year); if ($yearTime < time()) { $list[$key]['overTimeUnExpend'] = 365; } } } return $list; } // 客户消费等级批量变更 public static function updateCustomExpenseLevel($data) { $shopId = $data['shopId']; $mainId = $data['mainId']; $customs = self::getAllByCondition(['shopId' => $shopId], null, 'id, name, hdId, userId, buyAmount, growth, member, memberName, discount', null, true); $levelDatas = MemberLevelClass::getAllByCondition(['mainId' => $mainId], 'amount DESC', 'id, name, level, amount, discount'); if (!empty($customs) && !empty($levelDatas)) { foreach ($customs as $c) { // $buyAmount = 0; // // 获取所有零售订单,计算消费金额 // $orderList = \bizHd\order\classes\OrderClass::getAllByCondition(['customId' => $c->id], null, 'id, actPrice'); // if (!empty($orderList)) { // foreach ($orderList as $order) { // $buyAmount = bcadd($buyAmount, $order['actPrice'], 2); // } // } // $c->buyAmount = $buyAmount; // 根据 growth 设置等级 $memberData = self::getCustomExpenseLevel($c->growth, $mainId, $levelDatas); // 设置等级多处需要同步修改的,请搜索:getCustomExpenseLevel if ($c->member != $memberData['level'] || $c->memberName != $memberData['name']) { $hdId = $c->hdId; $hd = HdClass::getById($hdId, true); $change = [ 'customId' => $c->id, 'customName' => $c->name, 'hdId' => $hd->id, 'hdName' => $hd->name, 'mainId' => $mainId, 'shopId' => $shopId, 'targetId' => 0, 'rechargeAmount' => 0, 'days' => 0, 'daysBefore' => '0000-00-00 00:00:00', 'daysAfter' => '0000-00-00 00:00:00', 'beforeLevel' => $c->member, 'afterLevel' => $memberData['level'], 'beforeName' => $c->memberName, 'afterName' => $memberData['name'], 'style' => 1, //手动修改会员,脚本执行 'event' => '系统脚本修改', 'staffId' => 0, 'staffName' => '', ]; MemberChangeClass::add($change); } $c->member = $memberData['level']; $c->memberName = $memberData['name']; $c->discount = bcdiv(floatval($memberData['discount']), 10, 2); $c->save(); if (!empty($c->hdId)) { $hd = HdClass::getById($c->hdId, true); if (!empty($hd)) { $hd->member = $memberData['level']; $hd->memberName = $memberData['name']; $hd->discount = $c->discount; $hd->save(); } } // 更新客户所对应花店的消费金额 // HdClass::updateByCondition(['shopId'=>$shopId, 'userId'=>$c->userId], ['expendAmount'=>$buyAmount]); } } return true; } // 获取客户消费等级 public static function getCustomExpenseLevel($growth, $mainId, $levelDatas=[]) { if (empty($levelDatas)) { $levelDatas = MemberLevelClass::getAllByCondition(['mainId' => $mainId], 'amount DESC', 'id, name, level, amount, discount'); } foreach ($levelDatas as $data) { if ($growth >= $data['amount']) { return $data; } } return ['level'=>0, 'name'=>'', 'discount'=>10]; } }