- 新增只读取值接口 shipping-data/suggest:按字段去重返回匹配候选(供应商/店铺名称/仓库/快递公司,快递取 express_name1/2 并集) - ShippingOrderRepository::distinctValues 去重查询 - 前端 typeahead:输入弹下拉,支持鼠标点选与上下键/回车选择 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014nvGZkg27YMEdhRy6EiUwf
176 lines
5.6 KiB
PHP
176 lines
5.6 KiB
PHP
<?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): array
|
||
{
|
||
$page = max($page, 1);
|
||
$perPage = min(max($perPage, 1), 100);
|
||
|
||
$q = $this->filteredQuery($filters);
|
||
|
||
$total = (clone $q)->count();
|
||
$rows = (clone $q)
|
||
->orderByDesc('ship_time')
|
||
->orderByDesc('id')
|
||
->forPage($page, $perPage)
|
||
->get()
|
||
->map(fn ($r) => (array) $r)
|
||
->all();
|
||
|
||
return ['data' => $rows, 'total' => $total];
|
||
}
|
||
|
||
/**
|
||
* @param array<string,string> $filters
|
||
* @return \Generator<int,array<string,mixed>>
|
||
*/
|
||
public function exportRows(array $filters, int $batchSize = 1000): \Generator
|
||
{
|
||
$batchSize = min(max($batchSize, 1), 5000);
|
||
$lastId = null;
|
||
|
||
while (true) {
|
||
$query = $this->filteredQuery($filters);
|
||
if ($lastId !== null) {
|
||
$query->where('id', '>', $lastId);
|
||
}
|
||
|
||
$rows = $query->orderBy('id')->limit($batchSize)->get()->all();
|
||
if ($rows === []) {
|
||
return;
|
||
}
|
||
|
||
foreach ($rows as $row) {
|
||
yield (array) $row;
|
||
}
|
||
|
||
$lastId = (int) $rows[array_key_last($rows)]->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']));
|
||
}
|
||
|
||
foreach (['barcode', 'supplier_name', 'shop_name', 'warehouse_name'] as $column) {
|
||
if (($filters[$column] ?? '') !== '') {
|
||
$query->whereRaw($column . " LIKE ? ESCAPE '!'", [$this->contains((string) $filters[$column])]);
|
||
}
|
||
}
|
||
|
||
if (($filters['express_company'] ?? '') !== '') {
|
||
$value = $this->contains((string) $filters['express_company']);
|
||
$query->where(static function (Builder $courier) use ($value): void {
|
||
$courier->whereRaw("express_name1 LIKE ? ESCAPE '!'", [$value])
|
||
->orWhereRaw("express_name2 LIKE ? ESCAPE '!'", [$value]);
|
||
});
|
||
}
|
||
|
||
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>
|
||
*/
|
||
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), 50);
|
||
$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);
|
||
if ($v !== '') {
|
||
$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];
|
||
}
|
||
}
|
||
}
|