164 lines
5.9 KiB
PHP
164 lines
5.9 KiB
PHP
<?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');
|
||
}
|
||
}
|