> */ public function shipmentsFor(array $rows): array { if ($rows === []) return []; $db = $this->mirror->connection(); $from = null; $to = null; $tracking = []; $orders = []; foreach ($rows as $row) { [$start, $end] = self::window($row); $from = $from === null || $start < $from ? $start : $from; $to = $to === null || $end > $to ? $end : $to; $numbers = self::numbers($row['tracking_no'] ?? ''); if ($numbers !== []) { array_push($tracking, ...$numbers); } else { array_push($orders, ...self::numbers($row['online_order_no'] ?? '')); } } $found = []; // 只读每月已完成的代次;单号独立索引,合并订单也按完整单号匹配。 foreach (['tracking' => $tracking, 'order' => $orders] as $kind => $values) { foreach (array_chunk(array_values(array_unique($values)), 150) as $chunk) { $query = $db->table(Shipping::REFS . ' AS f') ->join(Shipping::ROWS . ' AS r', function ($join) { $join->on('r.generation', '=', 'f.generation')->on('r.source_row', '=', 'f.source_row'); }) ->join(Shipping::MONTHS . ' AS m', 'm.active_generation', '=', 'r.generation') ->where('f.kind', $kind)->whereIn('f.reference', $chunk) ->where('m.source_month', '>=', substr($from, 0, 10))->where('m.source_month', '<', substr($to, 0, 10)) ->where(function ($q) use ($from, $to) { $q->whereNull('r.ship_time')->orWhere(function ($q) use ($from, $to) { $q->where('r.ship_time', '>=', $from)->where('r.ship_time', '<', $to); }); })->select('r.*'); foreach ($query->cursor() as $record) { $shipment = (array) $record; $shipment['id'] = $record->generation . ':' . $record->source_row; $shipment['tracking_numbers'] = array_values(array_unique(array_merge(self::numbers($record->express_no), self::numbers($record->old_express_no)))); $shipment['order_numbers'] = self::numbers($record->trade_no); $shipment['source_id'] = hash('sha256', self::identifier($record->express_no) . '|' . $record->package_no); $found[$shipment['id']] = $shipment; if (count($found) > 20000) throw new RuntimeException('匹配发货记录过多,须缩小工单范围'); } } } return array_values($found); } public function readyMonths(): array { return $this->mirror->connection()->table(Shipping::MONTHS)->whereNotNull('active_generation') ->pluck('source_month')->map(static fn ($date) => substr($date, 0, 7))->all(); } /** @return array 规范化 SKU => 当前商品资料成本单价 */ public function costsFor(array $skus): array { if ($skus === []) return []; $dataset = $this->datasets->get(ProductCatalogMirrorRepository::DATASET_KEY); if (!$this->mirror->ready($dataset)) { throw new RuntimeException('商品资料尚未完成首次同步'); } $db = $this->mirror->connection(); $table = $dataset->targetTable(); $values = []; foreach ($skus as $sku) { $normalized = self::sku($sku); foreach ([$normalized, mb_strtolower($normalized), str_replace('+', '_X002B_', $normalized)] as $value) { if ($value !== '') $values[$value] = true; } } $result = []; $latest = []; $exclusions = Schema::connection($db->getName())->hasTable('product_catalog_exclusions'); foreach (array_chunk(array_keys($values), 150) as $chunk) { $query = $db->table($table)->where('sku_type', 'normal')->whereIn('sku_id', $chunk); if ($exclusions) { $query->whereNotExists(static function ($q) use ($table): void { $q->selectRaw('1')->from('product_catalog_exclusions AS e') ->whereColumn('e.mirror_key', $table . '._mirror_key') ->whereRaw("e.sku_id_normalized = REPLACE(UPPER(TRIM({$table}.sku_id)), '_X002B_', '+')"); }); } foreach ($query->orderByDesc('autoid')->get(['autoid', 'sku_id', 'cost_price']) as $record) { $key = self::sku($record->sku_id); if (!isset($latest[$key]) || (int) $record->autoid > $latest[$key]) { $latest[$key] = (int) $record->autoid; $result[$key] = $record->cost_price === null ? null : (string) $record->cost_price; } } } return $result; } /** 记账所在月份及前五个月,右端为下月1日(不含)。 */ public static function window(array $row): array { $month = Carbon::parse((string) $row['started_at'])->startOfMonth(); return [$month->copy()->subMonthsNoOverflow(5)->format('Y-m-d H:i:s'), $month->addMonth()->format('Y-m-d H:i:s')]; } public static function numbers(mixed $value): array { return array_values(array_unique(array_filter(array_map(static fn ($v) => self::identifier($v), preg_split('/[\r\n,,、;;]+/u', (string) $value) ?: []), static fn ($v) => $v !== ''))); } public static function sku(mixed $value): string { return str_replace('_X002B_', '+', mb_strtoupper(self::identifier($value))); } /** 只移除单号/商品编码外围的复制附加符,保留内部符号及前导0。 */ private static function identifier(mixed $value): string { return preg_replace('/^[\s\p{Cf}\'"‘’“”'"@@]+|[\s\p{Cf}\'"‘’“”'"@@]+$/u', '', (string) $value) ?? trim((string) $value); } }