CaiWuXiTong/modules/EcommerceFinance/Repositories/ShippingOrderRepository.php
limeng 440f06079f feat(发货数据): 供应商/店铺/仓库/快递公司改为勾选式多选下拉
- 下拉面板:回车检索 + 清除 + 表头全选 + 复选框列表,勾选多个即可
- 后端多选精确匹配:仓库/店铺/供应商 whereIn;快递公司匹配快递名称1/2 任一
- suggest 接口支持 limit(≤500),过滤空值与占位脏值"0"
- 查询与导出均按所选多个值生效

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014nvGZkg27YMEdhRy6EiUwf
2026-09-21 03:54:48 +00:00

208 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\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']));
}
// 条形码:单值关键词
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];
}
}
}