CaiWuXiTong/modules/EcommerceFinance/Services/SupplierBookkeepingProductResolver.php

121 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Modules\EcommerceFinance\Services;
use Modules\EcommerceFinance\Repositories\ProductCatalogMirrorRepository;
use Modules\EcommerceFinance\Repositories\SupplierBookkeepingValuationRepository as Shipping;
use Throwable;
/** 缺供应商的工单,先还原普通商品编码,再按商品资料补充供应商。 */
class SupplierBookkeepingProductResolver
{
public function __construct(
private readonly ProductCatalogMirrorRepository $products,
private readonly Shipping $shipping,
) {}
public function apply(array $rows): array
{
$missing = array_filter($rows, static fn ($row) => trim((string) ($row['supplier'] ?? '')) === '');
if ($missing === []) return $rows;
try {
$plans = [];
$codes = [];
foreach ($missing as $key => $row) {
$original = array_map([Shipping::class, 'sku'], Shipping::numbers($row['product_code'] ?? ''));
$plans[$key] = [];
foreach ($original as $code) {
$parts = self::encodedComponents($code);
$plans[$key][$code] = $parts;
array_push($codes, $code, ...$parts);
}
}
$catalog = $this->catalog($codes);
$needsShipping = [];
foreach ($plans as $key => $plan) {
// 运营套装的编号本身不含组件,必须有原单发货明细作依据。
if (count($plan) === 1) {
$code = (string) array_key_first($plan);
if (!isset($catalog[$code]) && preg_match('/^(?:MKH-)?运营组合套装/u', $code)) $needsShipping[$key] = $missing[$key];
}
}
if ($needsShipping !== []) {
$shipments = $this->shipping->shipmentsFor(array_values($needsShipping));
$extraCodes = [];
foreach ($needsShipping as $key => $row) {
$parts = $this->shippedComponents($row, $shipments);
$plans[$key][array_key_first($plans[$key])] = $parts;
array_push($extraCodes, ...$parts);
}
$catalog += $this->catalog($extraCodes);
}
foreach ($plans as $key => $plan) {
$resolved = [];
$suppliers = [];
$unresolved = [];
$details = [];
foreach ($plan as $code => $parts) {
// 普通商品精确命中优先,不拆合法商品编码内部的连字符。
foreach (isset($catalog[$code]) ? [$code] : ($parts !== [] ? $parts : [$code]) as $part) {
$resolved[$part] = true;
$supplier = trim((string) ($catalog[$part]['supplier_name'] ?? ''));
$details[$part] = $part . '→' . ($supplier !== '' ? $supplier : '未匹配供应商');
if ($supplier === '') $unresolved[] = $part;
else $suppliers[$supplier] = true;
}
}
$rows[$key]['resolved_product_code'] = implode("\n", array_keys($resolved));
$rows[$key]['supplier_match_basis'] = '按商品编码匹配商品资料:' . implode(';', $details);
// 任一组件未匹配时不使用部分供应商作扣款判定。
if ($resolved !== [] && $unresolved === []) $rows[$key]['supplier'] = implode("\n", array_keys($suppliers));
}
} catch (Throwable $exception) {
report($exception);
foreach (array_keys($missing) as $key) $rows[$key]['supplier_match_basis'] = '商品资料或原单发货明细暂不可读取,供应商待确认';
}
return $rows;
}
private function catalog(array $codes): array
{
$result = [];
foreach (array_chunk(array_values(array_unique($codes)), 150) as $chunk) {
foreach ($this->products->normalProductsBySkuCodes($chunk) as $product) {
$code = Shipping::sku($product->sku_id);
if (!isset($result[$code]) || (int) $product->autoid > (int) $result[$code]['autoid']) $result[$code] = (array) $product;
}
}
return $result;
}
/** 仅识别完整的“ZH-/MKH- 商品编码-数量”结构,组件仍须逐个命中普通商品资料。 */
private static function encodedComponents(string $code): array
{
if (!preg_match('/^(?:ZH-|MKH-)(.+)$/u', $code, $prefix)) return [];
preg_match_all('/(.+?)-([1-9]\d*)(?:-|$)/u', $prefix[1], $matches, PREG_SET_ORDER);
if (count($matches) < 2 || implode('', array_column($matches, 0)) !== $prefix[1]) return [];
return array_values(array_unique(array_map(static fn ($match) => Shipping::sku($match[1]), $matches)));
}
private function shippedComponents(array $row, array $shipments): array
{
[$from, $to] = Shipping::window($row);
$tracking = Shipping::numbers($row['tracking_no'] ?? '');
$orders = Shipping::numbers($row['online_order_no'] ?? '');
$wanted = $tracking !== [] ? $tracking : $orders;
if ($wanted === []) return [];
$matched = [];
$codes = [];
foreach ($shipments as $shipment) {
$references = $tracking !== [] ? $shipment['tracking_numbers'] : $shipment['order_numbers'];
if (array_intersect($wanted, $references) === []) continue;
if ($orders !== [] && array_intersect($orders, $shipment['order_numbers']) === []) continue;
if ($shipment['ship_time'] === null || $shipment['ship_time'] < $from || $shipment['ship_time'] >= $to) continue;
if (!in_array(trim((string) ($shipment['unshipped'] ?? '')), ['', '#N/A'], true)) return [];
array_push($matched, ...$references);
array_push($codes, ...array_map([Shipping::class, 'sku'], Shipping::numbers($shipment['barcode'])));
}
return array_diff($wanted, $matched) === [] ? array_values(array_unique($codes)) : [];
}
}