'正常记账', self::MODE_DROPSHIP_WRONG => '仅代发仓发错货记账', self::MODE_NONE => '不记账', ]; public const STATUS_DEDUCT = '扣款'; public const STATUS_SKIP = '不扣款'; public const STATUS_REVIEW = '待确认'; public const STATUSES = [self::STATUS_DEDUCT, self::STATUS_SKIP, self::STATUS_REVIEW]; public const DROPSHIP_WAREHOUSE_KEYWORD = '代发仓'; public const WAREHOUSE_WRONG_KEYWORD = '仓库发错货'; /** * 解析配置表中“厂家是否记账”原文,例如“代发仓发货错需记账,其他原因均不记账”“不记账(2025.3月-2027.12.30)”。 * * @return array{mode:string,effective_from:?string,effective_to:?string} */ public static function parseRuleText(string $text): array { $normalized = str_replace(['(', ')', ' ', ' '], ['(', ')', '', ''], trim($text)); if ($normalized === '') { throw new InvalidArgumentException('厂家是否记账为空。'); } $label = array_search($normalized, self::MODES, true); if (is_string($label)) { return ['mode' => $label, 'effective_from' => null, 'effective_to' => null]; } if (str_contains($normalized, '代发仓') && (str_contains($normalized, '发货错') || str_contains($normalized, '发错货'))) { $mode = self::MODE_DROPSHIP_WRONG; } elseif (str_starts_with($normalized, '正常记账')) { $mode = self::MODE_NORMAL; } elseif (str_starts_with($normalized, '不记账')) { $mode = self::MODE_NONE; } else { throw new InvalidArgumentException('无法识别的记账方式:' . $text); } [$from, $to] = [null, null]; $date = '(\d{4})[.\/年](\d{1,2})月?(?:[.\/](\d{1,2})日?)?'; if (preg_match('/(' . $date . '[-—~至]+' . $date . ')/u', $normalized, $m)) { $from = self::date((int) $m[1], (int) $m[2], ($m[3] ?? '') === '' ? 1 : (int) $m[3]); $to = self::date((int) $m[4], (int) $m[5], ($m[6] ?? '') === '' ? null : (int) $m[6]); if ($from > $to) { throw new InvalidArgumentException('生效期开始晚于结束:' . $text); } } elseif (str_contains($normalized, '(')) { throw new InvalidArgumentException('无法识别的生效期:' . $text); } return ['mode' => $mode, 'effective_from' => $from, 'effective_to' => $to]; } /** 配置的展示文字,例如“不记账(2025-03-01 至 2026-02-28)”。 */ public static function describe(array|object $config): string { $config = (array) $config; $label = self::MODES[$config['mode']] ?? (string) $config['mode']; $from = $config['effective_from'] ?? null; $to = $config['effective_to'] ?? null; if ($from === null && $to === null) { return $label; } return $label . '(' . ($from ?? '不限') . ' 至 ' . ($to ?? '不限') . ')'; } /** * @param array $row 厂家记账明细行 * @param array|object> $configs 供应商名称 => 生效中的配置 * @return array{supplier_config:string,deduction_status:string,deduction_basis:string} */ public static function decide(array $row, array $configs): array { $level1 = (string) ($row['reason_level_1'] ?? ''); $level2 = (string) ($row['reason_level_2'] ?? ''); $date = substr((string) ($row['started_at'] ?? ''), 0, 10); $suppliers = array_values(array_unique(array_filter(array_map('trim', explode("\n", (string) ($row['supplier'] ?? '')))), SORT_STRING)); $found = []; foreach ($suppliers as $supplier) { $found[$supplier] = isset($configs[$supplier]) ? (array) $configs[$supplier] : null; } $configText = implode("\n", array_map(static fn (?array $config): string => $config === null ? '未在配置表(按正常记账)' : self::describe($config), $found)); if ($level1 === '仓库记账') { $warehouse = (string) ($row['shipping_warehouse'] ?? ''); if (! str_contains($warehouse, self::DROPSHIP_WAREHOUSE_KEYWORD) || ! str_contains($level2, self::WAREHOUSE_WRONG_KEYWORD)) { return self::result($configText, self::STATUS_SKIP, '仓库记账仅“代发仓 + 仓库发错货”作为厂家扣款,本单不扣'); } } elseif ($level1 !== '厂家记账') { return self::result($configText, self::STATUS_SKIP, '非厂家记账/仓库记账原因'); } if ($suppliers === []) { return self::result('', self::STATUS_REVIEW, '工单未带供应商,无法判定扣款,请人工确认'); } $outcomes = []; foreach ($found as $supplier => $config) { $outcomes[$supplier] = self::outcome($level1, $config, $date); } $statuses = array_values(array_unique(array_column($outcomes, 0))); if (count($statuses) > 1) { $detail = implode(';', array_map(static fn (string $supplier, array $o): string => $supplier . ':' . $o[0], array_keys($outcomes), $outcomes)); return self::result($configText, self::STATUS_REVIEW, '多个供应商判定不一致,请人工确认(' . $detail . ')'); } return self::result($configText, $statuses[0], reset($outcomes)[1]); } /** @return array{0:string,1:string} [判定, 说明] */ private static function outcome(string $level1, ?array $config, string $date): array { if ($config === null) { return [self::STATUS_DEDUCT, $level1 === '厂家记账' ? '供应商不在配置表,按正常记账扣款' : '代发仓发错货,记厂家扣款(供应商不在配置表)']; } $from = $config['effective_from'] ?? null; $to = $config['effective_to'] ?? null; if (($from !== null && $date < $from) || ($to !== null && $date > $to)) { return [self::STATUS_DEDUCT, '发起日期不在配置生效期内(' . self::describe($config) . '),按正常记账扣款']; } return match ($config['mode']) { self::MODE_NONE => [self::STATUS_SKIP, '供应商配置为不记账'], self::MODE_NORMAL => $level1 === '厂家记账' ? [self::STATUS_DEDUCT, '供应商正常记账'] : [self::STATUS_DEDUCT, '代发仓发错货,记厂家扣款'], self::MODE_DROPSHIP_WRONG => $level1 === '仓库记账' ? [self::STATUS_DEDUCT, '代发仓发错货,记厂家扣款'] : [self::STATUS_SKIP, '供应商仅代发仓发错货记账,其他原因不记账'], default => [self::STATUS_REVIEW, '无法识别的记账方式,请人工确认'], }; } private static function result(string $config, string $status, string $basis): array { return ['supplier_config' => $config, 'deduction_status' => $status, 'deduction_basis' => $basis]; } private static function date(int $year, int $month, ?int $day): string { if ($month < 1 || $month > 12) { throw new InvalidArgumentException('生效期月份无效。'); } $last = (int) date('t', mktime(0, 0, 0, $month, 1, $year)); $day ??= $last; if ($day < 1 || $day > $last) { throw new InvalidArgumentException('生效期日期无效。'); } return sprintf('%04d-%02d-%02d', $year, $month, $day); } }