115 lines
5.0 KiB
PHP
115 lines
5.0 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Repositories;
|
||
|
||
use Illuminate\Support\Carbon;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Schema;
|
||
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
|
||
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 = DB::connection((string) config('jushuitan.connection', 'jushuitan_local'));
|
||
if (!Schema::connection($db->getName())->hasTable('jst_oms_shipping')) {
|
||
throw new RuntimeException('发货数据尚未就绪');
|
||
}
|
||
$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 (['express_no' => $tracking, 'trade_no' => $orders] as $column => $values) {
|
||
foreach (array_chunk(array_values(array_unique($values)), 150) as $chunk) {
|
||
$query = $db->table('jst_oms_shipping')->whereIn($column, $chunk)
|
||
->where('ship_time', '>=', $from)->where('ship_time', '<', $to)
|
||
->select(['id', 'source_id', 'express_no', 'trade_no', 'barcode', 'qty', 'freight', 'ship_time'])
|
||
->orderBy('id');
|
||
foreach ($query->lazyById(500) as $record) {
|
||
$found[$record->id] = (array) $record;
|
||
}
|
||
}
|
||
}
|
||
return array_values($found);
|
||
}
|
||
|
||
/** @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('trim', preg_split('/[\r\n,,、;;]+/u', (string) $value) ?: []), static fn ($v) => $v !== '')));
|
||
}
|
||
|
||
public static function sku(mixed $value): string
|
||
{
|
||
return str_replace('_X002B_', '+', mb_strtoupper(trim((string) $value)));
|
||
}
|
||
}
|