185 lines
6.1 KiB
PHP
185 lines
6.1 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Repositories;
|
||
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Schema;
|
||
|
||
/**
|
||
* 订单售后费用明细:聚水潭「商品利润 → 导出明细数据 → 订单售后费用数据」原表。
|
||
*
|
||
* 原表 rpt.jst_goods_profit_kpi_items_afs 的列名是 undefined1..undefinedN,
|
||
* 中文表头放在 rpt.jst_goods_profit_kpi_items_afs_schemas(column → name)。
|
||
* 两张表都镜像到共享业务库后,页面按 schemas 映射读取字段。
|
||
*/
|
||
class GoodsProfitAfsRepository
|
||
{
|
||
public const MAIN_DATASET = 'operations_replica.rpt.jst_goods_profit_kpi_items_afs';
|
||
|
||
public const SCHEMA_DATASET = 'operations_replica.rpt.jst_goods_profit_kpi_items_afs_schemas';
|
||
|
||
private const MAIN_TABLE = 'ops_jst_goods_profit_kpi_items_afs';
|
||
|
||
private const SCHEMA_TABLE = 'ops_jst_goods_profit_kpi_items_afs_schemas';
|
||
|
||
/** @var array<string,string>|null */
|
||
private ?array $fieldColumns = null;
|
||
|
||
public function ready(): bool
|
||
{
|
||
try {
|
||
return Schema::connection($this->connectionName())->hasTable(self::MAIN_TABLE)
|
||
&& Schema::connection($this->connectionName())->hasTable(self::SCHEMA_TABLE)
|
||
&& $this->connection()->table(self::MAIN_TABLE)->exists();
|
||
} catch (\Throwable) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 页面字段键 => 上游列名(undefinedN)。
|
||
*
|
||
* @return array<string,string>
|
||
*/
|
||
public function fieldColumns(): array
|
||
{
|
||
if ($this->fieldColumns !== null) {
|
||
return $this->fieldColumns;
|
||
}
|
||
|
||
$headers = (array) config('order_after_sales_cost.headers', []);
|
||
$map = [];
|
||
|
||
foreach ($this->connection()->table(self::SCHEMA_TABLE)->orderBy('id')->get(['column', 'name']) as $schema) {
|
||
$label = self::normalizeLabel((string) $schema->name);
|
||
$field = $headers[$label] ?? null;
|
||
|
||
// 同一科目出现多次(如 800105佣金 / 800105平台佣金)时保留第一次映射。
|
||
if ($field !== null && ! isset($map[$field])) {
|
||
$map[$field] = (string) $schema->column;
|
||
}
|
||
}
|
||
|
||
return $this->fieldColumns = $map;
|
||
}
|
||
|
||
/** @param array<string,mixed> $filters */
|
||
public function covers(array $filters): bool
|
||
{
|
||
return $this->ready() && $this->filtered($filters, $this->fieldColumns())->limit(1)->exists();
|
||
}
|
||
|
||
/**
|
||
* @param array<string,mixed> $filters
|
||
* @return array{rows:list<array<string,mixed>>,has_more:bool}
|
||
*/
|
||
public function paginate(array $filters, int $page, int $limit): array
|
||
{
|
||
$page = max($page, 1);
|
||
$limit = min(max($limit, 1), 100);
|
||
$columns = $this->fieldColumns();
|
||
$select = array_values(array_unique(array_merge(['id'], array_values($columns))));
|
||
$orderColumn = $columns['order_time'] ?? null;
|
||
|
||
$query = $this->filtered($filters, $columns)->select($select);
|
||
|
||
if ($orderColumn !== null) {
|
||
$query->orderByDesc($orderColumn);
|
||
}
|
||
|
||
$records = $query->orderByDesc('id')->forPage($page, $limit + 1)->get();
|
||
$numberKeys = array_flip((array) config('order_after_sales_cost.number_keys', []));
|
||
$rows = [];
|
||
|
||
foreach ($records as $record) {
|
||
$row = [];
|
||
|
||
foreach ($columns as $field => $column) {
|
||
$value = $record->{$column} ?? null;
|
||
|
||
if (isset($numberKeys[$field])) {
|
||
$value = $value === null ? null : (float) $value;
|
||
}
|
||
|
||
$row[$field] = is_string($value) ? trim($value) : $value;
|
||
}
|
||
|
||
$rows[] = $row;
|
||
}
|
||
|
||
return [
|
||
'rows' => array_slice($rows, 0, $limit),
|
||
'has_more' => count($rows) > $limit,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param array<string,mixed> $filters
|
||
* @param array<string,string> $columns
|
||
*/
|
||
private function filtered(array $filters, array $columns): \Illuminate\Database\Query\Builder
|
||
{
|
||
$query = $this->connection()->table(self::MAIN_TABLE);
|
||
$from = trim((string) ($filters['from'] ?? ''));
|
||
$to = trim((string) ($filters['to'] ?? ''));
|
||
$orderNo = trim((string) ($filters['order_no'] ?? ''));
|
||
$sku = trim((string) ($filters['sku_id'] ?? ''));
|
||
$shops = array_values(array_filter((array) ($filters['shop_names'] ?? []), static fn ($v) => trim((string) $v) !== ''));
|
||
|
||
if ($from !== '' && isset($columns['order_time'])) {
|
||
$query->where($columns['order_time'], '>=', $from . ' 00:00:00');
|
||
}
|
||
|
||
if ($to !== '' && isset($columns['order_time'])) {
|
||
$query->where($columns['order_time'], '<=', $to . ' 23:59:59');
|
||
}
|
||
|
||
if ($shops !== [] && isset($columns['shop_name'])) {
|
||
$query->whereIn($columns['shop_name'], $shops);
|
||
}
|
||
|
||
if ($orderNo !== '') {
|
||
$query->where(function ($inner) use ($columns, $orderNo): void {
|
||
$matched = false;
|
||
|
||
foreach (['online_order_no', 'internal_order_no'] as $field) {
|
||
if (! isset($columns[$field])) {
|
||
continue;
|
||
}
|
||
|
||
$inner->orWhere($columns[$field], 'like', $orderNo . '%');
|
||
$matched = true;
|
||
}
|
||
|
||
if (! $matched) {
|
||
$inner->whereRaw('1 = 0');
|
||
}
|
||
});
|
||
}
|
||
|
||
if ($sku !== '' && isset($columns['product_code'])) {
|
||
$query->where($columns['product_code'], 'like', '%' . addcslashes($sku, '%_\\') . '%');
|
||
}
|
||
|
||
return $query;
|
||
}
|
||
|
||
private function connection(): \Illuminate\Database\ConnectionInterface
|
||
{
|
||
return DB::connection($this->connectionName());
|
||
}
|
||
|
||
private function connectionName(): string
|
||
{
|
||
return (string) config('upstream_mirrors.connection', 'business_mirror');
|
||
}
|
||
|
||
private static function normalizeLabel(string $label): string
|
||
{
|
||
$label = str_replace(["\u{3000}", "\u{00A0}"], ' ', $label);
|
||
$label = preg_replace('/\s+/u', ' ', $label) ?? $label;
|
||
|
||
return trim($label);
|
||
}
|
||
}
|