CaiWuXiTong/modules/EcommerceFinance/Repositories/CostFreightAnomalyRepository.php
renshen 1b6ff0bb75 feat: 新增成本运费异常监控 (#992)
成本/运费异常页监控商品成本和快递费同时缺失的订单。
2026-11-07 18:04:44 +08:00

164 lines
5.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\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Modules\EcommerceFinance\Services\ShengsuanOrderProfitRowMapper;
class CostFreightAnomalyRepository
{
private const TABLE = ShengsuanOrderProfitRowMapper::TABLE;
public function ready(): bool
{
try {
$schema = Schema::connection($this->connectionName());
return $schema->hasTable(self::TABLE)
&& $schema->hasColumns(self::TABLE, [
'shop_name', 'online_order_no', 'internal_order_no',
'ship_time', 'product_cost', 'matched_freight_fee',
'matched_package_fee', 'matched_bill_fee',
]);
} catch (\Throwable) {
return false;
}
}
/** @return array{ready:bool,last_synced_at:?string,total:int,shops:list<string>} */
public function status(): array
{
try {
$query = $this->connection()->table(self::TABLE);
return [
'ready' => $this->ready(),
'last_synced_at' => (clone $query)->max('synced_at'),
'total' => (int) (clone $query)->count(),
'shops' => (clone $query)->whereNotNull('shop_name')
->where('shop_name', '<>', '')
->distinct()
->orderBy('shop_name')
->pluck('shop_name')
->map(static fn (mixed $shop): string => trim((string) $shop))
->filter()
->values()
->all(),
];
} catch (\Throwable) {
return [
'ready' => false,
'last_synced_at' => null,
'total' => 0,
'shops' => [],
];
}
}
/**
* @param array<string,mixed> $filters
* @return array{rows:list<array<string,mixed>>,total:int,has_more:bool}
*/
public function paginate(array $filters, int $page, int $limit): array
{
$query = $this->filtered($this->connection()->table(self::TABLE), $filters);
$total = (int) (clone $query)->count();
$rows = $query
->select([
'shop_name', 'online_order_no', 'internal_order_no', 'order_key',
'ship_time', 'product_cost', 'matched_package_fee',
'matched_freight_fee', 'matched_bill_fee', 'synced_at',
])
->orderByDesc('ship_time')
->orderByDesc('order_key')
->forPage(max($page, 1), min(max($limit, 1), 100))
->get()
->map(static fn (object $row): array => [
'shop_name' => trim((string) ($row->shop_name ?? '')),
'online_order_no' => trim((string) ($row->online_order_no ?? '')),
'internal_order_no' => trim((string) ($row->internal_order_no ?? '')),
'order_key' => trim((string) ($row->order_key ?? '')),
'ship_time' => $row->ship_time,
'product_cost' => $row->product_cost === null ? null : (float) $row->product_cost,
'matched_package_fee' => $row->matched_package_fee === null ? null : (float) $row->matched_package_fee,
'matched_freight_fee' => $row->matched_freight_fee === null ? null : (float) $row->matched_freight_fee,
'matched_bill_fee' => $row->matched_bill_fee === null ? null : (float) $row->matched_bill_fee,
'synced_at' => $row->synced_at,
'anomaly_type' => '商品成本及快递费缺失',
])
->all();
return [
'rows' => $rows,
'total' => $total,
'has_more' => $page * min(max($limit, 1), 100) < $total,
];
}
private function filtered(\Illuminate\Database\Query\Builder $query, array $filters): \Illuminate\Database\Query\Builder
{
$query->where(function ($cost) {
$cost->whereNull('product_cost')->orWhere('product_cost', 0);
})->where(function ($freight) {
$freight->whereNull('matched_freight_fee')->orWhere('matched_freight_fee', 0);
});
$from = trim((string) ($filters['from'] ?? ''));
$to = trim((string) ($filters['to'] ?? ''));
if ($from !== '') {
$query->where('ship_time', '>=', $from . ' 00:00:00');
}
if ($to !== '') {
$query->where('ship_time', '<=', $to . ' 23:59:59');
}
$shops = array_values(array_unique(array_filter(array_map(
static fn (mixed $value): string => trim((string) $value),
(array) ($filters['shop_names'] ?? []),
))));
if ($shops !== []) {
$query->whereIn('shop_name', array_slice($shops, 0, 50));
}
$orders = $this->batchValues((string) ($filters['order_no'] ?? ''));
if ($orders !== []) {
$query->where(function ($order) use ($orders): void {
$order->whereIn('online_order_no', $orders)
->orWhereIn('internal_order_no', $orders);
});
}
return $query;
}
/** @return list<string> */
private function batchValues(string $value): array
{
$parts = preg_split('/[\s,,;;]+/u', trim($value)) ?: [];
$values = [];
foreach ($parts as $part) {
$part = trim($part);
if ($part !== '') {
$values[$part] = true;
}
if (count($values) >= 100) {
break;
}
}
return array_keys($values);
}
private function connection(): ConnectionInterface
{
return DB::connection($this->connectionName());
}
private function connectionName(): string
{
return (string) config('upstream_mirrors.connection', 'business_mirror');
}
}