CaiWuXiTong/modules/EcommerceFinance/Repositories/InstallationDetailMirrorRepository.php

233 lines
13 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\Support\Facades\Schema;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
/** 只读安装明细镜像;页面不得回退直连 JianSun。 */
class InstallationDetailMirrorRepository
{
public const DATASET = 'jiansun.dbo.fys_Ticketing_install_view';
public const ITEM_DATASET = 'jiansun.dbo.fys_Ticketing_install_item';
public function __construct(
private readonly GenericBusinessMirrorRepository $mirror,
private readonly BusinessMirrorDatasetRegistry $datasets,
)
{
}
/** @return array{data:list<array<string,mixed>>,total:int,ready:bool} */
public function paginate(int $page, int $perPage, array $filters = []): array
{
$connection = $this->mirror->connection();
$dataset = $this->datasets->get(self::DATASET);
$items = $this->datasets->get(self::ITEM_DATASET);
if (! $this->ready($dataset, $items, $connection->getName())) {
return ['data' => [], 'total' => 0, 'ready' => false];
}
$perPage = min(max($perPage, 1), 100);
// 主视图按商品行展开,同一安装单会出现多行;主表只保留每个
// SourceGuid 的最新 Id,商品行通过 details() 单独展开。
$latest = $connection->table($dataset->targetTable())
->selectRaw('MAX(Id) AS id')
->groupBy('SourceGuid');
$query = $connection->table($dataset->targetTable() . ' AS v')
->joinSub($latest, 'latest', static fn ($join) => $join->on('latest.id', '=', 'v.Id'))
->select('v.*');
$this->applyFilters($query, $filters, $items->targetTable());
$total = (clone $query)->count();
$rows = $query->orderByDesc('v.Id')->forPage(max($page, 1), $perPage)->get();
return ['data' => $rows->map(fn (object $row): array => $this->map((array) $row))->all(), 'total' => $total, 'ready' => true];
}
/** @return array{data:list<array<string,mixed>>,ready:bool} */
public function details(string $guid): array
{
$dataset = $this->datasets->get(self::DATASET);
$items = $this->datasets->get(self::ITEM_DATASET);
$connection = $this->mirror->connection();
if (! $this->ready($dataset, $items, $connection->getName())) {
return ['data' => [], 'ready' => false];
}
$mainExists = $connection->table($dataset->targetTable())
->where('SourceGuid', $guid)
->exists();
if (! $mainExists) {
return ['data' => [], 'ready' => true];
}
$transactionNo = $connection->table($dataset->targetTable())
->where('SourceGuid', $guid)
->orderByDesc('Id')->value('OrderNum');
$rows = $connection->table($items->targetTable())
->where('guid', $guid)
->orderBy('Id')
->limit(101)
->get();
return ['data' => $rows->take(100)->map(fn (object $row): array => $this->mapItem((array) $row, $transactionNo))->all(), 'ready' => true, 'has_more' => $rows->count() > 100];
}
private function ready($dataset, $items, string $connection): bool
{
return $this->mirror->ready($dataset) && $this->mirror->ready($items)
&& Schema::connection($connection)->hasTable($dataset->targetTable())
&& Schema::connection($connection)->hasTable($items->targetTable());
}
/** @return array{rows:int,requested:float,settled:float,paid:float} */
public function summary(array $filters = []): array
{
$dataset = $this->datasets->get(self::DATASET);
$connection = $this->mirror->connection();
if (! $this->mirror->ready($dataset) || ! Schema::connection($connection->getName())->hasTable($dataset->targetTable())) {
return ['rows' => 0, 'requested' => 0.0, 'settled' => 0.0, 'paid' => 0.0];
}
$latest = $connection->table($dataset->targetTable())
->selectRaw('MAX(Id) AS id')
->groupBy('SourceGuid');
$query = $connection->table($dataset->targetTable() . ' AS v')
->joinSub($latest, 'latest', static fn ($join) => $join->on('latest.id', '=', 'v.Id'));
$this->applyFilters($query, $filters, $this->datasets->get(self::ITEM_DATASET)->targetTable());
$row = $query
->selectRaw('COUNT(*) AS row_count, COALESCE(SUM(v.installcost),0) AS requested, COALESCE(SUM(v.settleAmount),0) AS settled, COALESCE(SUM(v.acceptAmount),0) AS paid')
->first();
return ['rows' => (int) $row->row_count, 'requested' => (float) $row->requested, 'settled' => (float) $row->settled, 'paid' => (float) $row->paid];
}
public static function requestFilters(): array
{
$rules = [];
foreach (['created', 'customer_review', 'finance_review'] as $prefix) {
$rules[$prefix . '_from'] = ['nullable', 'date_format:Y-m-d'];
$rules[$prefix . '_to'] = ['nullable', 'date_format:Y-m-d'];
if (request()->filled($prefix . '_from')) $rules[$prefix . '_to'][] = 'after_or_equal:' . $prefix . '_from';
}
foreach (['push_status','order_no','customer_review','finance_review','quote_status','arranged_status','fee_payer','completed_status','shop_name','delete_status','installation_company','installation_no','order_type','product_id'] as $key) {
$rules[$key] = ['nullable', 'string', 'max:191'];
}
foreach (['customer_review', 'finance_review', 'arranged_status', 'completed_status', 'delete_status'] as $key) $rules[$key][] = 'in:0,1';
return validator(request()->only(array_keys($rules)), $rules)->validate();
}
private function applyFilters($query, array $filters, string $itemTable): void
{
foreach (['created'=>'SubmitTime', 'customer_review'=>'kfshTime', 'finance_review'=>'cwshTime'] as $prefix=>$column) {
if (!empty($filters[$prefix.'_from'])) $query->where('v.'.$column, '>=', $filters[$prefix.'_from']);
if (!empty($filters[$prefix.'_to'])) $query->where('v.'.$column, '<', \Illuminate\Support\Carbon::parse($filters[$prefix.'_to'])->addDay()->toDateString());
}
foreach (['push_status'=>'apistatus','order_no'=>'OrderNum','customer_review'=>'kfsh','finance_review'=>'cwsh','quote_status'=>'BidStatus','fee_payer'=>'costtaker','completed_status'=>'installComplete','shop_name'=>'shop_name','delete_status'=>'Delete','installation_company'=>'installCompanyName','installation_no'=>'ordernum_id','order_type'=>'TypeDto'] as $key=>$column) {
if (isset($filters[$key]) && $filters[$key] !== '') $query->where('v.'.$column, $filters[$key]);
}
if (isset($filters['arranged_status']) && $filters['arranged_status'] !== '') {
$query->where('v.StatusId', $filters['arranged_status'] === '1' ? '>' : '<=', 0);
}
if (!empty($filters['product_id'])) {
$query->whereExists(function ($sub) use ($itemTable, $filters) {
$sub->selectRaw('1')->from($itemTable.' AS filter_item')
->whereColumn('filter_item.guid', 'v.SourceGuid')->where('filter_item.shopproid', $filters['product_id']);
});
}
}
/** @param array<string,mixed> $row */
private function map(array $row): array
{
$value = static function (array $row, string ...$names): mixed {
foreach ($names as $name) {
foreach ($row as $key => $value) if (strcasecmp((string) $key, $name) === 0) return $value;
}
return null;
};
$mapped = [
'id' => $value($row, 'detail_id', 'Id'), 'installation_no' => $value($row, 'ordernum_id', 'OrderNum'),
'delete_status' => $value($row, 'Delete'), 'quote_status' => $value($row, 'BidStatus'), 'quote_deadline' => $value($row, 'BidEndTime'),
'installation_company' => $value($row, 'installCompanyName'), 'submitted_at' => $value($row, 'SubmitTime'),
'shipped_at' => $value($row, 'ShipTIme'), 'buyer_nickname' => $value($row, 'BuyersNick'),
'store_name' => $value($row, 'shop_name', 'shopname'), 'installation_type' => $value($row, 'TypeDto'),
'pre_push_verification' => $value($row, 'checkMsg'), 'push_status' => $value($row, 'apistatus'),
'push_result' => $value($row, 'apiresult'), 'arranged_status' => $this->arranged($value($row, 'StatusId')),
'order_no' => $value($row, 'OrderNum'),
'item_quantity' => $value($row, 'Num'), 'buyer_name' => $value($row, 'BuyersName'), 'buyer_phone' => $value($row, 'Mobile'),
'notes' => $value($row, 'Nots'), 'province' => $value($row, 'Province'), 'city' => $value($row, 'City'),
'district' => $value($row, 'Area'), 'address' => $value($row, 'Address'), 'requested_amount' => $value($row, 'installcost'),
'settled_amount' => $value($row, 'settleAmount'), 'image_urls' => $value($row, 'Picture'),
'completed_status' => $this->yesNo($value($row, 'installComplete')),
'customer_service_review' => $this->review($value($row, 'kfsh')), 'customer_service_reviewed_at' => $value($row, 'kfshTime'),
'finance_review' => $this->review($value($row, 'cwsh')), 'finance_reviewed_at' => $value($row, 'cwshTime'),
'actual_install_quantity' => $value($row, 'installNum'), 'actual_install_fee' => $value($row, 'install_Cost'),
'actual_install_product_name' => $value($row, 'installGoodsName'), 'installer_phone' => $value($row, 'installTel'),
'installation_company_notes' => $value($row, 'installNote'), 'installation_fee_payer' => $value($row, 'costtaker'),
'payment_channel' => $value($row, 'costWay'), 'payment_transaction_no' => $value($row, 'actporderNum'),
'payment_voucher' => $value($row, 'costWayVoucher'), 'payment_amount' => $value($row, 'acceptAmount'),
'creator' => $value($row, 'Operation'), 'related_guid' => $value($row, 'detail_guid', 'SourceGuid'),
'product_id' => $value($row, 'shopproid'), 'auxiliary_spu' => $value($row, 'Spu'),
'product_store_code' => $value($row, 'shopskucode'), 'item_no' => $value($row, 'proCode'),
'barcode' => $value($row, 'barCode'), 'product_name' => $value($row, 'skuName'), 'supplier_name' => $value($row, 'scmname'),
'detail_requested_amount' => $value($row, 'applyItemAmount'), 'detail_requested_quantity' => $value($row, 'installcount'),
'detail_settled_amount' => $value($row, 'settleItemAmount'), 'detail_received_amount' => $value($row, 'acceptItemAmount'),
'change_reason' => $value($row, 'remark'), 'source_created_at' => $value($row, 'item_created_at'),
'source_modified_at' => $value($row, 'item_modified_at'),
];
// 页面按模板的完整 61 列渲染;视图暂未提供的字段必须显式为 null,
// 不能让 Blade 读取未定义属性。
return array_replace(array_fill_keys(array_keys(\App\Models\Business\InstallationDetail::columnLabels()), null), $mapped);
}
/** @param array<string,mixed> $row */
private function mapItem(array $row, mixed $transactionNo = null): array
{
$value = static function (array $row, string ...$names): mixed {
foreach ($names as $name) {
foreach ($row as $key => $value) {
if (strcasecmp((string) $key, $name) === 0) {
return $value;
}
}
}
return null;
};
return [
'transaction_no' => $value($row, 'Sourceno', 'source_no', 'orderNum', 'ordernum') ?? $transactionNo,
'product_id' => $value($row, 'shopproid'),
'id_owner' => $value($row, 'id_owner', 'idOwner'),
'department_owner' => $value($row, 'department_owner', 'departmentOwner'),
'auxiliary_spu' => $value($row, 'Spu'),
'product_store_code' => $value($row, 'shopskucode'),
'item_no' => $value($row, 'proCode'),
'barcode' => $value($row, 'barCode'),
'product_name' => $value($row, 'skuName'),
'supplier_name' => $value($row, 'scmname'),
'detail_requested_amount' => $value($row, 'applyItemAmount'),
'detail_requested_quantity' => $value($row, 'installcount'),
'detail_settled_amount' => $value($row, 'settleItemAmount'),
'detail_received_amount' => $value($row, 'acceptItemAmount'),
'change_reason' => $value($row, 'remark'),
'source_created_at' => $value($row, 'createTime'),
'source_modified_at' => $value($row, 'modifyTime'),
];
}
private function yesNo(mixed $value): ?string
{
return $value === null ? null : ((int) $value === 1 ? '是' : '否');
}
private function review(mixed $value): ?string
{
return $value === null ? null : ((int) $value === 1 ? '已审核' : '未审核');
}
private function arranged(mixed $value): ?string
{
return $value === null ? null : ((int) $value > 0 ? '已安排' : '未安排');
}
}