'送货', 1 => '自取', 2 => '跑腿', 3 => '物流', 4 => '快递' ]; for ($style = 0; $style <= 4; $style++) { $config = self::getByCondition(['mainId' => $mainId, 'style' => $style]); $sort = 0; $alias = $aliases[$style]; $status = 1; if (!empty($config)) { $sort = isset($config['sort']) ? (int)$config['sort'] : 0; $alias = !empty($config['name']) ? $config['name'] : $aliases[$style]; $status = isset($config['status']) ? (int)$config['status'] : 1; } $list[] = [ 'style' => $style, 'name' => $alias, 'sort' => $sort, 'status' => $status ]; } return $list; } /** * 获取指定门店和类型的配送方式配置,若不存在则进行默认初始化 * @param int $shopId 门店ID * @param int $mainId 商户ID * @param int $style 配送类型 (0: 送货, 1: 自取, 2: 跑腿, 3: 物流, 4: 快递) * @return array */ public static function getConfig($shopId, $mainId, $style) { // 复杂分支/关键逻辑:优化。后端的查询全部由 shopId 改为 mainId,只根据 mainId 和 style 进行查询。 $config = self::getByCondition(['mainId' => $mainId, 'style' => $style]); if (empty($config)) { // 从字典配置中读取对应类型的默认配置 $defaultConfig = dict::getDict('shMethod', $style); $initData = array_merge([ 'shopId' => $shopId, // shopId 仅作为保存预留字段存入 'mainId' => $mainId, 'style' => $style, ], $defaultConfig); $config = self::add($initData, true); $config = $config->toArray(); } // 获取关联的说明项 $config['explains'] = ShExplainClass::getAllByCondition( ['methodId' => $config['id']], 'sort ASC, id ASC' ); // 如果 is_跑腿 (style为2),获取关联的满减规则 if ($style == 2) { $config['reduceRules'] = ShReduceRuleClass::getAllByCondition( ['methodId' => $config['id']], 'sort ASC, id ASC' ); } else { $config['reduceRules'] = []; } return $config; } /** * 一次性获取商户所有的5种配送方式配置,减少数据库查询交互。 * 如果数据库里没有对应配置,则自动采用 dict.php 里的默认参数进行填充并完成入库。 * @param int $shopId 门店ID * @param int $mainId 商户ID * @return array */ public static function getAllConfigs($shopId, $mainId) { // 1. 一次性从数据库中查询当前商户的所有配送方式配置 $configsMap = self::getAllByCondition(['mainId' => $mainId], 'sort asc', '*', 'style', true); // 2. 补齐缺失的配送方式,并做初始化入库 for ($style = 0; $style <= 4; $style++) { if (!isset($configsMap[$style])) { $defaultConfig = dict::getDict('shMethod', $style); $initData = array_merge([ 'shopId' => $shopId, // shopId 仅作为保存预留字段存入 'mainId' => $mainId, 'style' => $style, ], $defaultConfig); $configObj = self::add($initData, true); $configsMap[$style] = $configObj; } } // 将 ActiveRecord 统一转换为 array,并提取 methodIds 用于批量子表查询 $configsArray = []; $methodIds = []; foreach ($configsMap as $style => $configObj) { $configArr = $configObj instanceof \yii\db\ActiveRecord ? $configObj->toArray() : $configObj; $configsArray[$style] = $configArr; $methodIds[] = (int)$configArr['id']; } // 3. 批量查询所有的说明项 (xhShExplain) - 仅 1 次查询 $allExplains = []; if (!empty($methodIds)) { $allExplains = ShExplainClass::getAllByCondition(['methodId' => ['in', $methodIds]], 'sort ASC, id ASC'); } $explainsMap = []; foreach ($allExplains as $exp) { $explainsMap[(int)$exp['methodId']][] = $exp; } // 4. 批量查询所有的跑腿满减规则 (xhShReduceRule) - 仅 1 次查询 $allReduceRules = []; if (!empty($methodIds)) { $allReduceRules = ShReduceRuleClass::getAllByCondition(['methodId' => ['in', $methodIds]], 'sort ASC, id ASC'); } $reduceRulesMap = []; foreach ($allReduceRules as $rule) { $reduceRulesMap[(int)$rule['methodId']][] = $rule; } // 5. 将批量查出的说明项和规则,合并填充到 5 个配送方式中 $result = []; for ($style = 0; $style <= 4; $style++) { $config = $configsArray[$style]; $methodId = (int)$config['id']; $config['explains'] = $explainsMap[$methodId] ?? []; $config['reduceRules'] = ($style == 2) ? ($reduceRulesMap[$methodId] ?? []) : []; $result[] = $config; } // 按 sort 升序返回,供 hd/ghs GetAllConfig 等接口展示自定义排序 usort($result, function ($a, $b) { $sortCompare = (int)($a['sort'] ?? 0) <=> (int)($b['sort'] ?? 0); if ($sortCompare !== 0) { return $sortCompare; } // sort 相同时按 style 稳定排序,避免顺序抖动 return (int)($a['style'] ?? 0) <=> (int)($b['style'] ?? 0); }); return $result; } /** * 判断配送方式配置是否未达最低消费(金额或扎数任一不满足即视为未达标) * 与 hdApp affirm-sh-method-panel.checkShMethodBelowMinimum 规则一致 * * @param array $config xhShMethod 配置 * @param float $actPrice 实付花材金额 * @param int|float $bigNum 下单扎数 * @return bool */ /** * 采购下单后校验 xhShMethod 配送限制 * 供 hd PurchaseController::actionCreateOrder 在 createPurchase 之后调用,与旧版 else 分支门店硬编码限制同级 * * @param int $shopId 供货商门店 ID * @param int $mainId 供货商商户 ID * @param int $sendType 配送方式,与 xhShMethod.style 一致 * @param float $actPrice 下单后实付花材金额 * @param int|float $bigNum 下单扎数 * @param float $packCost 已写入采购单的包装费 * @param float $sendCost 已写入采购单的运费 * @param string $wlName 物流公司名称(style=3 时必填) * @param int $customId 客户ID,用于判断当天是否已收过 * @param array|object $ghsInfo 客户与该供货商的关系信息(xhGhs) * @return string 空字符串表示通过,否则为需提示用户的错误文案 */ public static function checkLimit($shopId, $mainId, $sendType, $actPrice, $bigNum, $packCost = 0, $sendCost = 0, $wlName = '', $customId = 0, $ghsInfo = []) { $style = (int)$sendType; if ($style < 0 || $style > 4) { return '配送方式无效'; } $config = self::getConfig($shopId, $mainId, $style); if (empty($config)) { return '配送方式配置异常'; } if ($style === 0 && !empty($ghsInfo) && isset($ghsInfo['homeRule']) && $ghsInfo['homeRule'] == 1) { // 如果 homeRule == 1,特定字段取xhGhs的设置(优先级最高),其余(如name)保留原配置 $config['status'] = $ghsInfo['home'] ?? 0; $config['minAmount'] = $ghsInfo['homeAmount'] ?? 0; $config['minNum'] = $ghsInfo['homeNum'] ?? 0; $config['unMeet'] = $ghsInfo['homeUnMeet'] ?? 0; $config['unMeetFee'] = $ghsInfo['homeUnFee'] ?? 0; } $methodName = !empty($config['name']) ? $config['name'] : '该配送方式'; // 配送方式已禁用,对齐旧逻辑「暂不支持送货上门,请选其它配送方式」 if (isset($config['status']) && (int)$config['status'] === 0) { return "暂不支持{$methodName},请选其它配送方式"; } // 物流需填物流公司,与 hdApp validateBeforeSubmit 一致 if ($style === 3 && trim((string)$wlName) === '') { return '请填选物流'; } $minAmount = isset($config['minAmount']) ? (float)$config['minAmount'] : 0.00; $minNum = isset($config['minNum']) ? (int)$config['minNum'] : 0; // 必须金额和数量同时满足(如果设置了的话),才算“达标” $isBelowMinimum = false; if ($minAmount > 0 && $actPrice < $minAmount) { $isBelowMinimum = true; } if ($minNum > 0 && (float)$bigNum < $minNum) { $isBelowMinimum = true; } if (!$isBelowMinimum) { return ''; } $unMeet = isset($config['unMeet']) ? (int)$config['unMeet'] : 0; $unMeetFee = isset($config['unMeetFee']) ? (float)$config['unMeetFee'] : 0; // 不满最低消费且不允许下单 if ($unMeet === 2) { // 如果当天该配送方式已成功下过单,不再限制,直接放行 if (self::isPackPaidToday($customId, $style) || self::isSendCostPaidToday($customId, $style)) { return ''; } if ($minAmount > 0 && (float)$actPrice < $minAmount) { return "满{$minAmount}元 可{$methodName}"; } if ($minNum > 0 && (float)$bigNum < $minNum) { return "满{$minNum}扎 可{$methodName}"; } return '未达到最低消费,不能下单'; } // 不满最低消费但允许加收费用时,校验包装费/运费是否已写入采购单,防止前端绕过 if (($unMeet === 0 || $unMeet === 1) && $unMeetFee > 0) { // unMeet === 0 加收运费, unMeet === 1 加收包装费 $feeToCheck = $unMeet === 1 ? $packCost : $sendCost; // 如果今天已经收过对应的附加费用,则直接通过校验 if ($unMeet === 1 && self::isPackPaidToday($customId, $style)) { return ''; } if ($unMeet === 0 && self::isSendCostPaidToday($customId, $style)) { return ''; } if (bccomp((string)$feeToCheck, (string)$unMeetFee, 2) < 0) { $feeLabel = $unMeet === 1 ? '包装费' : '运费'; return "未达最低消费,需加收{$feeLabel}{$unMeetFee}元"; } } return ''; } /** * 判断当天是否已经收过附加费(包装费) * 与其他逻辑共用同一个 Redis Key,保证一天只收一次 * @param int $customId 客户ID * @param int $sendType 配送方式 (0: 送货, 4: 快递) * @return bool */ public static function isPackPaidToday($customId, $sendType = 0) { if (empty($customId)) { return false; } $current = date("Y_m_d"); $key = 'ghs_custom_today_has_get_pack_cost_' . $sendType . '_' . $current . '_' . $customId; $has = \Yii::$app->redis->executeCommand('GET', [$key]); return (!empty($has) && $has == 1); } /** * 判断当天是否已经收过运费 * @param int $customId 客户ID * @param int $sendType 配送方式 * @return bool */ public static function isSendCostPaidToday($customId, $sendType = 0) { if (empty($customId)) { return false; } $current = date("Y_m_d"); $key = 'ghs_custom_today_has_get_send_cost_' . $sendType . '_' . $current . '_' . $customId; $has = \Yii::$app->redis->executeCommand('GET', [$key]); return (!empty($has) && $has == 1); } /** * 计算采购单应写入的附加费用(不满最低消费时按 unMeet 加收运费或包装费) * 供 createPurchase 前写入 sendCost 和 packCost,与 hdApp syncPackCostByMethod 一致 * * @param int $shopId 供货商门店 ID * @param int $mainId 供货商商户 ID * @param int $sendType 配送方式 * @param float $itemTotalAmount 花材合计金额(下单前,与前端 commodityPrice 一致) * @param int|float $bigNum 下单扎数 * @param int $customId 客户ID,用于判断当天是否已收过 * @param array|object $ghsInfo 客户与该供货商的关系信息(xhGhs) * @return array ['sendCost' => 运费, 'packCost' => 包装费] */ public static function calcFee($config, $sendType, $itemTotalAmount, $bigNum, $customId = 0, $ghsInfo = []) { $costs = ['sendCost' => 0, 'packCost' => 0]; $style = (int)$sendType; if ($style < 0 || $style > 4) { return $costs; } if (empty($config)) { return $costs; } if ($style === 0 && !empty($ghsInfo) && isset($ghsInfo['homeRule']) && $ghsInfo['homeRule'] == 1) { // 如果 homeRule == 1,特定字段取xhGhs的设置(优先级最高),其余(如name)保留原配置 $config['status'] = $ghsInfo['home'] ?? 0; $config['minAmount'] = $ghsInfo['homeAmount'] ?? 0; $config['minNum'] = $ghsInfo['homeNum'] ?? 0; $config['unMeet'] = $ghsInfo['homeUnMeet'] ?? 0; $config['unMeetFee'] = $ghsInfo['homeUnFee'] ?? 0; } // 直接合并 isBelowMinimum 的逻辑,方便理解,不再套用额外的方法 $minAmount = isset($config['minAmount']) ? (float)$config['minAmount'] : 0.00; $minNum = isset($config['minNum']) ? (int)$config['minNum'] : 0; // 必须金额和数量同时满足(如果设置了的话),才算“达标” $isBelowMinimum = false; if ($minAmount > 0 && $itemTotalAmount < $minAmount) { $isBelowMinimum = true; } if ($minNum > 0 && (float)$bigNum < $minNum) { $isBelowMinimum = true; } // 如果已经达标,不收取附加费用 if (!$isBelowMinimum) { return $costs; } $unMeet = isset($config['unMeet']) ? (int)$config['unMeet'] : 0; $unMeetFee = isset($config['unMeetFee']) ? (float)$config['unMeetFee'] : 0; if ($unMeetFee > 0) { // unMeet: 0 加收运费(默认),1 加收包装费,2 不能下单 if ($unMeet === 0) { if (!self::isSendCostPaidToday($customId, $style)) { $costs['sendCost'] = $unMeetFee; } } elseif ($unMeet === 1) { if (!self::isPackPaidToday($customId, $style)) { $costs['packCost'] = $unMeetFee; } } } return $costs; } /** * 保存配送方式配置,包含说明项和满减规则 * @param int $shopId 门店ID * @param int $mainId 商户ID * @param array $data 配置数据 * @return bool * @throws \Exception */ public static function saveConfig($shopId, $mainId, $data) { $id = $data['id'] ?? 0; $style = $data['style'] ?? 0; $config = null; if ($id > 0) { // 复杂分支/关键逻辑:优化。后端的查询全部由 shopId 改为 mainId。 $config = self::getByCondition(['id' => $id, 'mainId' => $mainId], true); } if (empty($config)) { // 复杂分支/关键逻辑:优化。后端的查询全部由 shopId 改为 mainId。 $config = self::getByCondition(['mainId' => $mainId, 'style' => $style], true); } $saveData = [ 'name' => $data['name'] ?? '', 'calcType' => isset($data['calcType']) ? (int)$data['calcType'] : 0, // 算运费方式 (0: 用跑腿, 1: 用自定义) 'status' => isset($data['status']) ? (int)$data['status'] : 1, 'sort' => isset($data['sort']) ? (int)$data['sort'] : 0, 'minAmount' => isset($data['minAmount']) ? (float)$data['minAmount'] : 0.00, 'minNum' => isset($data['minNum']) ? (int)$data['minNum'] : 0, 'unMeet' => isset($data['unMeet']) ? (int)$data['unMeet'] : 0, 'unMeetFee' => isset($data['unMeetFee']) ? (float)$data['unMeetFee'] : 0.00, 'startKm' => isset($data['startKm']) ? (float)$data['startKm'] : 0.00, 'startPrice' => isset($data['startPrice']) ? (float)$data['startPrice'] : 0.00, 'perKmPrice' => isset($data['perKmPrice']) ? (float)$data['perKmPrice'] : 0.00, 'changeRate' => isset($data['changeRate']) ? (float)$data['changeRate'] : 0.00, ]; $transaction = Yii::$app->db->beginTransaction(); try { if (empty($config)) { $saveData['shopId'] = $shopId; // shopId 作为保存预留字段存入 $saveData['mainId'] = $mainId; $saveData['style'] = $style; $config = self::add($saveData, true); } else { self::updateByCondition(['id' => $config->id], $saveData); } $methodId = $config->id; // 1. 保存说明项 (xhShExplain) ShExplainClass::deleteByCondition(['methodId' => $methodId]); if (!empty($data['explains']) && is_array($data['explains'])) { $explainRows = []; foreach ($data['explains'] as $index => $exp) { if (empty($exp['explain'])) { continue; } $explainRows[] = [ 'methodId' => $methodId, 'explain' => $exp['explain'], 'color' => isset($exp['color']) ? (int)$exp['color'] : 1, 'fontWeight' => isset($exp['fontWeight']) ? (int)$exp['fontWeight'] : 1, 'sort' => isset($exp['sort']) ? (int)$exp['sort'] : $index, ]; } if (!empty($explainRows)) { ShExplainClass::batchAdd($explainRows); } } // 2. 保存跑腿满减规则 (xhShReduceRule) ShReduceRuleClass::deleteByCondition(['methodId' => $methodId]); if ($style == 2 && !empty($data['reduceRules']) && is_array($data['reduceRules'])) { $ruleRows = []; foreach ($data['reduceRules'] as $index => $rule) { $ruleRows[] = [ 'methodId' => $methodId, 'meetNum' => isset($rule['meetNum']) ? (int)$rule['meetNum'] : 0, 'meetAmount' => isset($rule['meetAmount']) ? (float)$rule['meetAmount'] : 0.00, 'freeKm' => isset($rule['freeKm']) ? (float)$rule['freeKm'] : 0.00, 'sort' => isset($rule['sort']) ? (int)$rule['sort'] : $index, ]; } if (!empty($ruleRows)) { ShReduceRuleClass::batchAdd($ruleRows); } } $transaction->commit(); return true; } catch (\Exception $e) { $transaction->rollBack(); throw $e; } } }