option('company'); if ($companyId <= 0 || ! Company::query()->where('id', $companyId)->exists()) { $this->error('请指定有效的 --company。'); return self::FAILURE; } $payload = json_decode((string) @file_get_contents((string) $this->argument('file')), true); $items = is_array($payload) ? ($payload['versions'] ?? null) : null; if (! is_array($items) || $items === []) { $this->error('文件不是有效的计费规则 JSON(缺少 versions)。'); return self::FAILURE; } $plan = []; $report = []; foreach ($items as $item) { $code = (string) ($item['version_id'] ?? ''); [$status, $note, $data] = $this->prepare($companyId, $item); $report[] = [$code, (string) ($item['company_name'] ?? ''), $status, $note]; if ($data !== null) { $plan[] = $data; } } $this->table(['版本', '快递公司', '结果', '说明'], $report); $counts = array_count_values(array_column($report, 2)); $summary = collect($counts)->map(fn ($n, $k) => "{$k} {$n}")->implode(','); if (isset($counts['错误'])) { $this->error('存在错误,整批未写入:' . $summary); return self::FAILURE; } if ($this->option('dry-run')) { $this->info('校验通过,未写入(dry-run):' . $summary); return self::SUCCESS; } DB::transaction(function () use ($plan): void { foreach ($plan as [$version, $structure, $rows]) { $version->forceFill(['billing_structure' => $structure, 'billing_rows' => $rows])->save(); } }); $this->info('导入完成:' . $summary); return self::SUCCESS; } /** * @param array $item * @return array{0:string,1:string,2:?array} */ private function prepare(int $companyId, array $item): array { $code = (string) ($item['version_id'] ?? ''); $version = CourierQuoteVersion::query()->where('company_id', $companyId)->where('version_code', $code)->first(); if ($version === null) { return ['错误', '系统中找不到该版本号', null]; } if (trim((string) $version->company_name) !== trim((string) ($item['company_name'] ?? ''))) { return ['错误', '快递名称不一致:系统为「' . $version->company_name . '」', null]; } if (($item['conversion'] ?? '') === 'manual') { return ['跳过', '特殊计费(' . ($item['manual_reason'] ?? '按体积/条码') . '),需人工核算', null]; } $source = (array) ($item['structure'] ?? []); $segments = ($item['conversion'] ?? '') === 'needs_segment_mode'; $mode = $segments ? CourierQuoteStructure::SEGMENTS : (string) ($source['mode'] ?? ''); if (! in_array($mode, self::MODES, true)) { return ['错误', '不支持的计费方式:' . $mode, null]; } $params = (array) ($item['params'] ?? []); $perTicket = 0.0; foreach ((array) ($params['surcharges'] ?? []) as $surcharge) { if (($surcharge['condition'] ?? '') === '每票' && is_numeric($surcharge['amount'] ?? null)) { $perTicket += (float) $surcharge['amount']; } } $structure = [ 'mode' => $mode, 'flat_bands' => $segments ? [] : $this->numbers($source['flat_bands'] ?? []), 'then_mode' => $segments ? '' : (string) ($source['then_mode'] ?? ''), 'first_weight' => ! $segments && is_numeric($source['first_weight'] ?? null) ? (float) $source['first_weight'] : null, 'rate_tiers' => $segments ? [] : $this->numbers($source['rate_tiers'] ?? []), 'rounding' => in_array($source['rounding'] ?? 'none', array_keys(CourierQuoteStructure::ROUNDING), true) ? (string) ($source['rounding'] ?? 'none') : 'none', 'volumetric_divisor' => is_numeric($params['volumetric_divisor'] ?? null) ? (int) $params['volumetric_divisor'] : null, 'weight_limit' => is_numeric($params['weight_limit_kg'] ?? null) ? (float) $params['weight_limit_kg'] : null, 'min_weight' => is_numeric($source['min_weight'] ?? $params['min_weight_kg'] ?? null) ? (float) ($source['min_weight'] ?? $params['min_weight_kg']) : null, 'per_ticket_surcharge' => $perTicket > 0 ? $perTicket : null, 'notes' => array_values(array_map('strval', (array) ($params['notes'] ?? []))), 'imported_from' => 'stock-quote-billing-import', ]; $rows = []; foreach ((array) ($segments ? ($item['segments_rows'] ?? []) : ($item['rows'] ?? [])) as $row) { $region = trim((string) ($row['region'] ?? '')); if ($region === '') { return ['错误', '存在没有地区名的行', null]; } if ($segments) { $list = []; foreach ((array) ($row['segments'] ?? []) as $s) { $list[] = array_filter([ 'lo' => (float) ($s['lo'] ?? 0), 'hi' => is_numeric($s['hi'] ?? null) ? (float) $s['hi'] : null, 'type' => (string) ($s['type'] ?? ''), 'price' => $this->number($s['price'] ?? null), 'base' => $this->number($s['base'] ?? null), 'rate' => $this->number($s['rate'] ?? null), 'fw' => $this->number($s['fw'] ?? null), ], static fn ($v, $k) => $v !== null || $k === 'hi', ARRAY_FILTER_USE_BOTH); } $rows[] = ['region' => $region, 'segments' => $list]; } else { $rows[] = [ 'region' => $region, 'flat' => array_map(fn ($v) => $this->number($v), array_values((array) ($row['flat'] ?? []))), 'base' => $this->number($row['base'] ?? null), 'rates' => array_map(fn ($v) => $this->number($v), array_values((array) ($row['rates'] ?? []))), ]; } } if ($rows === []) { return ['错误', '没有任何地区价格', null]; } $existing = $version->billing_structure; $note = CourierQuoteStructure::describe($structure) . ',' . count($rows) . ' 个地区'; if (is_array($existing) && $existing !== []) { if ($existing == $structure && $version->billing_rows == $rows) { return ['跳过', '已导入且内容相同', null]; } if (! $this->option('force')) { return ['错误', '已有不同的计费规则,如需覆盖请加 --force', null]; } return ['覆盖', $note, [$version, $structure, $rows]]; } return ['写入', $note, [$version, $structure, $rows]]; } private function number(mixed $value): ?float { return is_numeric($value) ? round((float) $value, 4) : null; } /** @return list */ private function numbers(mixed $values): array { return array_values(array_map('floatval', array_filter((array) $values, 'is_numeric'))); } }