CaiWuXiTong/modules/EcommerceFinance/Repositories/SupplierBookkeepingValuationRepository.php

138 lines
6.6 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\Repositories;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use Modules\EcommerceFinance\Services\SupplierBookkeepingShippingImporter as Shipping;
use RuntimeException;
/** 厂家记账只读核价:工作平台月度导出镜像 + 当前普通商品资料成本。 */
class SupplierBookkeepingValuationRepository
{
public function __construct(
private readonly GenericBusinessMirrorRepository $mirror,
private readonly BusinessMirrorDatasetRegistry $datasets,
) {}
/** @return list<array<string,mixed>> */
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<string,?string> 规范化 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);
}
}