CaiWuXiTong/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php

272 lines
9.9 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\Database\ConnectionInterface;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
/**
* 发货明细只读仓库。读取扁平表 jst_oms_shipping(对齐工作平台发货数据 47 列),
* 供“发货数据”页面展示与后续快递运费核算使用。
*/
class ShippingOrderRepository
{
private readonly ConnectionInterface $db;
public function __construct()
{
$this->db = DB::connection((string) config('jushuitan.connection', 'jushuitan_local'));
}
/**
* @param array<string,string> $filters
* @return array{data:array,total:int}
*/
public function paginate(array $filters, int $page, int $perPage, string $mode = 'merged'): array
{
$page = max($page, 1);
$perPage = min(max($perPage, 1), 100);
if ($mode === 'detail') {
$q = $this->filteredQuery($filters);
$total = (clone $q)->count();
$rows = (clone $q)
->orderByDesc('ship_time')
->orderByDesc('source_id')
->orderBy('sku_source_id')
->forPage($page, $perPage)
->get()->map(fn ($r) => (array) $r)->all();
return ['data' => $rows, 'total' => $total];
}
// 合并:按包裹(source_id)归并
$base = $this->filteredQuery($filters);
$total = (clone $base)->distinct()->count('source_id');
$rows = (clone $base)
->groupBy('source_id')
->selectRaw($this->mergedSelectRaw())
->orderByDesc('ship_time')
->orderByDesc('source_id')
->forPage($page, $perPage)
->get()->map(fn ($r) => (array) $r)->all();
return ['data' => $rows, 'total' => $total];
}
/** 合并聚合 SELECT:条码/商品ID 逗号拼接、数量/重量/体积/金额/运费求和、长宽高取最大、其余取值。 */
private function mergedSelectRaw(): string
{
$sum = ['qty', 'freight', 'online_weight', 'actual_weight', 'volume_cm3', 'weight_kg',
'settle_weight_kg', 'settle_volume_cm3', 'recon_weight_kg', 'recon_volume_cm3', 'amount', 'settle_freight'];
$concat = ['item_id', 'barcode'];
$min = ['product_name', 'supplier_name', 'product_code'];
$all = ['express_no', 'trade_no', 'item_id', 'barcode', 'product_name', 'qty', 'pkg_count',
'receiver_state', 'buyer_nick', 'receiver_name', 'shop_name', 'express_name1', 'warehouse_name',
'ship_time', 'freight', 'online_weight', 'actual_weight', 'length_cm', 'width_cm', 'height_cm',
'volume_cm3', 'receiver_city', 'receiver_district', 'supplier_name', 'receiver_address',
'receiver_mobile', 'receiver_phone', 'express_name2', 'platform', 'buyer_memo', 'seller_memo',
'package_no', 'amount', 'product_code', 'download_time', 'pay_time', 'freight_rate', 'freight_formula',
'weight_kg', 'settle_weight_kg', 'settle_volume_cm3', 'recon_weight_kg', 'recon_volume_cm3',
'finished_barcode', 'finished_name', 'finished_supplier', 'settle_freight', 'front_no'];
$parts = ['MAX(id) as id', 'source_id'];
foreach ($all as $c) {
if (in_array($c, $sum, true)) {
$parts[] = "SUM(`{$c}`) as `{$c}`";
} elseif (in_array($c, $concat, true)) {
$parts[] = "GROUP_CONCAT(DISTINCT `{$c}` SEPARATOR ',') as `{$c}`";
} elseif (in_array($c, $min, true)) {
$parts[] = "MIN(`{$c}`) as `{$c}`";
} else {
$parts[] = "MAX(`{$c}`) as `{$c}`";
}
}
return implode(', ', $parts);
}
/**
* @param array<string,string> $filters
* @return \Generator<int,array<string,mixed>>
*/
public function exportRows(array $filters, int $batchSize = 1000, string $mode = 'merged'): \Generator
{
$batchSize = min(max($batchSize, 1), 5000);
$last = null;
if ($mode === 'detail') {
while (true) {
$query = $this->filteredQuery($filters);
if ($last !== null) {
$query->where('id', '>', $last);
}
$rows = $query->orderBy('id')->limit($batchSize)->get()->all();
if ($rows === []) {
return;
}
foreach ($rows as $row) {
yield (array) $row;
}
$last = (int) $rows[array_key_last($rows)]->id;
if (count($rows) < $batchSize) {
return;
}
}
}
// 合并导出:按 source_id 键集分页 + 归并
while (true) {
$query = $this->filteredQuery($filters);
if ($last !== null) {
$query->where('source_id', '>', $last);
}
$rows = $query->groupBy('source_id')->selectRaw($this->mergedSelectRaw())
->orderBy('source_id')->limit($batchSize)->get()->all();
if ($rows === []) {
return;
}
foreach ($rows as $row) {
yield (array) $row;
}
$last = (int) $rows[array_key_last($rows)]->source_id;
if (count($rows) < $batchSize) {
return;
}
}
}
/** @param array<string,string> $filters */
private function filteredQuery(array $filters): Builder
{
$query = $this->db->table('jst_oms_shipping');
if (($filters['express_no'] ?? '') !== '') {
$query->where('express_no', '=', trim((string) $filters['express_no']));
}
if (($filters['sourceno'] ?? '') !== '') {
$query->where('trade_no', '=', trim((string) $filters['sourceno']));
}
// 条形码:单值关键词
if (($filters['barcode'] ?? '') !== '') {
$query->whereRaw("barcode LIKE ? ESCAPE '!'", [$this->contains((string) $filters['barcode'])]);
}
// 供应商/店铺/仓库:多选,精确匹配所选的任意一个
foreach (['supplier_name', 'shop_name', 'warehouse_name'] as $column) {
$values = $this->multi($filters[$column] ?? null);
if ($values !== []) {
$query->whereIn($column, $values);
}
}
// 快递公司:多选,匹配快递名称1/2 任意一个
$couriers = $this->multi($filters['express_company'] ?? null);
if ($couriers !== []) {
$query->where(function (Builder $group) use ($couriers): void {
$group->whereIn('express_name1', $couriers)
->orWhereIn('express_name2', $couriers);
});
}
if (($filters['ship_from'] ?? '') !== '') {
$query->where('ship_time', '>=', $filters['ship_from'] . ' 00:00:00');
}
if (($filters['ship_to'] ?? '') !== '') {
$query->where('ship_time', '<', date('Y-m-d 00:00:00', strtotime($filters['ship_to'] . ' +1 day')));
}
return $query;
}
private function contains(string $value): string
{
return '%' . str_replace(['!', '%', '_'], ['!!', '!%', '!_'], trim($value)) . '%';
}
/**
* 归一多选筛选值为字符串数组(兼容单个字符串)。
* @return array<int,string>
*/
private function multi(mixed $value): array
{
if (is_string($value)) {
$value = $value === '' ? [] : [$value];
}
if (! is_array($value)) {
return [];
}
$out = [];
foreach ($value as $item) {
$item = trim((string) $item);
if ($item !== '' && ! in_array($item, $out, true)) {
$out[] = $item;
}
}
return array_slice($out, 0, 50);
}
/**
* 取某字段去重候选值(供筛选框自动补全)。
* @return array<int,string>
*/
public function distinctValues(string $field, string $keyword, int $limit = 20): array
{
$map = [
'supplier_name' => ['supplier_name'],
'shop_name' => ['shop_name'],
'warehouse_name' => ['warehouse_name'],
'express_company' => ['express_name1', 'express_name2'],
];
if (! isset($map[$field])) {
return [];
}
$limit = min(max($limit, 1), 500);
$like = $this->contains($keyword);
$values = [];
foreach ($map[$field] as $col) {
$rows = $this->db->table('jst_oms_shipping')
->selectRaw($col . ' as v')
->whereRaw($col . " LIKE ? ESCAPE '!'", [$like])
->whereNotNull($col)
->where($col, '<>', '')
->groupBy($col)
->orderBy($col)
->limit($limit)
->pluck('v');
foreach ($rows as $v) {
$v = trim((string) $v);
// 过滤空值与占位脏值(如自动发货仓 send_warehouse 为 "0")
if ($v !== '' && $v !== '0') {
$values[$v] = true;
}
}
}
$values = array_keys($values);
sort($values, SORT_FLAG_CASE | SORT_STRING);
return array_slice($values, 0, $limit);
}
/** 同步状态(供页面显示同步中/最近完成)。 */
public function syncStatus(): array
{
try {
$row = $this->db->table('jst_sync_state')->where('dataset_key', 'jst.sctrade')->first();
if (! $row) {
return ['code' => 'unavailable', 'last_succeeded_at' => null];
}
return [
'code' => (string) ($row->status ?? 'unavailable'),
'last_succeeded_at' => $row->last_succeeded_at ?? null,
];
} catch (\Throwable) {
return ['code' => 'unavailable', 'last_succeeded_at' => null];
}
}
}