CaiWuXiTong/app/Business/KukaPostingBase/KukaPostingBasePageData.php
chenxi 27021ce55a feat: 顾家发货明细补齐物料与成本 (#1117)
按货号同步唯一物料编号,并从顾家报价单带出物料描述和含税成本,按实发数量计算发货金额。
2026-11-11 20:08:21 +08:00

183 lines
7.4 KiB
PHP

<?php
namespace App\Business\KukaPostingBase;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Http\Request;
use Throwable;
class KukaPostingBasePageData
{
public function __construct(private readonly KukaPostingBaseRepository $repository)
{
}
/**
* @return array{
* directory: array<string, mixed>,
* rows: mixed,
* filters: array<string, mixed>,
* periods: list<string>,
* statuses: list<string>,
* stores: list<string>,
* import: ?array<string, mixed>,
* summary: array<string, mixed>,
* error: ?string
* }
*/
public function build(Request $request, int $companyId): array
{
$stores = array_values((array) config('kuka_posting.stores', []));
$fields = array_values((array) config('kuka_posting.fields', []));
$available = $this->repository->available();
$periods = $available ? $this->repository->periods($companyId) : [];
$latest = $available ? $this->repository->latestImport($companyId) : null;
$period = trim((string) $request->query('period', ''));
if (preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $period) !== 1) {
$period = (string) ($latest->period ?? ($periods[0] ?? now()->format('Y-m')));
}
$storeName = trim((string) $request->query('store_name', ''));
if ($storeName !== '' && ! in_array($storeName, $stores, true)) {
$storeName = '';
}
$status = mb_substr(trim((string) $request->query('status', '')), 0, 64);
$keyword = mb_substr(trim((string) $request->query('keyword', '')), 0, 120);
$perPage = (int) $request->integer('per_page', 50);
if (! in_array($perPage, [50, 100, 200], true)) {
$perPage = 50;
}
$page = max(1, (int) $request->integer('page', 1));
$filters = [
'period' => $period,
'store_name' => $storeName,
'status' => $status,
'keyword' => $keyword,
'per_page' => $perPage,
];
$error = $available ? null : '顾家过账基础数据镜像尚未建立,请先执行数据库迁移。';
try {
$rows = $available
? $this->repository->paginate($companyId, $period, $filters, $page, $perPage)
: new LengthAwarePaginator([], 0, $perPage, $page, [
'path' => $request->url(),
'query' => $request->query(),
]);
$decodedRows = $rows->getCollection()->map(function (object $row): array {
$values = json_decode((string) $row->source_values, true);
if (! is_array($values)) {
$values = [];
}
$values['_row_id'] = $row->id;
return [
'item_number' => trim((string) ($values['货号'] ?? '')),
'values' => $values,
];
});
$maps = $this->repository->materialMaps(
$companyId,
$decodedRows->pluck('item_number')->filter()->unique()->values()->all(),
);
$quotations = $this->repository->effectiveQuotations(
$companyId,
collect($maps)->pluck('material_number')->filter()->unique()->values()->all(),
);
$rows->setCollection($decodedRows->map(function (array $row) use ($maps, $quotations): object {
$values = $row['values'];
$materialNumber = trim((string) ($maps[$row['item_number']]->material_number ?? ''));
$quotation = $materialNumber === '' ? null : ($quotations[$materialNumber] ?? null);
$values['物料编号'] = '';
$values['型号'] = '';
$values['成本'] = '';
$values['发货金额'] = '';
if ($quotation !== null) {
$cost = $this->decimal($quotation->price_including_tax);
$quantity = $this->decimal($values['实发数量'] ?? null);
$values['物料编号'] = $materialNumber;
$values['型号'] = (string) ($quotation->material_desc ?? '');
$values['成本'] = $cost ?? '';
$values['发货金额'] = $cost !== null && $quantity !== null
? $this->multiplyDecimal($cost, $quantity)
: '';
}
return (object) $values;
}));
$statuses = $available ? $this->repository->statuses($companyId, $period) : [];
} catch (Throwable $exception) {
report($exception);
$statuses = [];
$error = '顾家过账基础数据暂时无法读取,请稍后重试;这不代表业务数据为 0。';
$rows = new LengthAwarePaginator([], 0, $perPage, $page, [
'path' => $request->url(),
'query' => $request->query(),
]);
}
$import = $available ? $this->repository->importFor($companyId, $period) : null;
$importData = null;
if ($import !== null) {
$storeCounts = json_decode((string) ($import->store_counts ?? ''), true);
$importData = [
'period' => (string) $import->period,
'row_count' => (int) $import->row_count,
'store_count' => (int) $import->store_count,
'order_count' => (int) $import->order_count,
'store_counts' => is_array($storeCounts) ? $storeCounts : [],
'file_name' => (string) $import->file_name,
'file_sha256' => (string) $import->file_sha256,
'source_sheet' => (string) $import->source_sheet,
'completed_at' => (string) $import->completed_at,
];
}
return [
'directory' => [
'icon' => 'dataset',
'status' => $importData === null ? '尚未导入' : '聚水潭原始明细',
'description' => '聚水潭“销售主题分析(新版)· 商品利润 · 明细(订单商品)”原始导出。仅保留指定 12 家店铺的 ' . $period . ' 数据。',
'empty' => '该账期还没有导入顾家过账基础数据。',
'fields' => $fields,
],
'rows' => $rows,
'filters' => $filters,
'periods' => $periods,
'statuses' => $statuses,
'stores' => $stores,
'import' => $importData,
'summary' => [
'filtered_rows' => $rows->total(),
'rows' => (int) ($importData['row_count'] ?? 0),
'stores' => (int) ($importData['store_count'] ?? 0),
'orders' => (int) ($importData['order_count'] ?? 0),
'completed_at' => $importData['completed_at'] ?? null,
],
'error' => $error,
];
}
private function decimal(mixed $value): ?string
{
$normalized = str_replace(',', '', trim((string) $value));
return $normalized !== '' && is_numeric($normalized) ? $normalized : null;
}
private function multiplyDecimal(string $cost, string $quantity): string
{
if (function_exists('bcmul')) {
return bcmul($cost, $quantity, 4);
}
return number_format((float) $cost * (float) $quantity, 4, '.', '');
}
}