CaiWuXiTong/modules/EcommerceFinance/View/PlantingReimbursementPageData.php
limeng ea479733ab feat(种菜数据汇总): 接入聚工单种菜报销,新增种菜损耗扣款页面
- 种菜报销:复用已登记镜像 jiansun.dbo.jst_workOrderInfo,工单类型“种菜报销”、状态已完成,
  按完成时间归属报销月份(M 月 = [M-02 00:00, M+1-02 00:00)),自 2026-09-02 起;同一工单只取最新一版
- 种菜损耗:当月种菜报销的线上订单号匹配上月+本月发货数据(jst_oms_shipping)交易单号;
  任务备注/卖家备注含“拍A发B/拍A发A”的不扣款(单独导出发运营),只发信誉订单虚拟条码/透明挂钩的不扣款,
  其余按发货条码在普通商品资料(ops_jst_sku)的成本价 × 数量 × 1.2 扣款,成本价缺失单独提示
- 两页均支持筛选、列设置、CSV 导出;按月缓存 10 分钟
- 测试:窗口口径、免扣字样、虚拟条码/挂钩、成本价取数与缺失、页面筛选和导出

涉及:金额计算展示(只读);不涉及数据库迁移、权限模型和上游写入。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VANWYsNUbu2piMtCKymoyd
2026-10-19 14:37:09 +08:00

106 lines
4.5 KiB
PHP

<?php
namespace Modules\EcommerceFinance\View;
use Illuminate\Pagination\LengthAwarePaginator;
use Modules\EcommerceFinance\Repositories\PlantingReimbursementRepository as Repository;
/** 种菜数据汇总 → 种菜报销(聚工单打款工单“种菜报销”,已完成,完成时间自 2026-09-02 起)。 */
class PlantingReimbursementPageData
{
use PlantingPageFilters;
public const TITLE = '种菜报销';
public const PER_PAGE = 50;
public const TEXT_FILTERS = [
'work_order_no' => '工单编号',
'online_order_no' => '线上订单号',
'shop_name' => '店铺名称',
'task_remark' => '任务备注',
'seller_remark' => '卖家备注',
'payee_name' => '信誉账号名称',
];
public const COLUMNS = [
'completed_at' => '完成时间', 'started_at' => '发起时间', 'work_order_no' => '工单编号', 'status' => '工单状态',
'platform' => '平台', 'shop_name' => '店铺名称', 'online_order_no' => '线上订单号', 'internal_order_no' => '内部单号',
'task_remark' => '任务备注', 'seller_remark' => '卖家备注',
'actual_paid_amount' => '实付金额', 'task_payment' => '任务付款金额', 'task_freight' => '任务快递费',
'task_commission' => '任务佣金', 'jingxi_price_diff' => '京喜差价', 'task_deduction' => '任务扣款费用',
'task_empty_package_fee' => '任务空包费', 'reimbursement_amount' => '最终报销金额',
'payee_name' => '信誉账号名称', 'payee_account' => '信誉账号', 'payee_bank' => '信誉账号开户行', 'payee_mobile' => '收款人手机号',
'initiator' => '发起人', 'executor' => '执行人', 'completer' => '完成人',
'product_code' => '商品编码', 'product_name' => '商品名称', 'product_quantity' => '商品数量', 'platform_product_id' => '平台商品ID',
'erp_order_status' => 'ERP订单状态', 'platform_order_status' => '平台订单状态', 'paid_at' => '付款时间',
];
public const MONEY_COLUMNS = Repository::MONEY_FIELDS;
public function __construct(private readonly Repository $repository) {}
/** @return array<string,string> */
public function filters(): array
{
return $this->baseFilters();
}
/**
* @param array<string,string> $filters
* @param list<array<string,mixed>>|null $rows
* @return \Generator<int,array<string,mixed>>
*/
public function rows(array $filters, ?array $rows = null): \Generator
{
foreach ($rows ?? $this->repository->rows($filters['month']) as $row) {
if ($this->matchesText($row, $filters)) {
yield $row;
}
}
}
public function page(): array
{
$filters = $this->filters();
$page = max(1, (int) request('page', 1));
$sync = $this->repository->syncStatus();
$summary = ['count' => 0, 'task_payment' => 0, 'reimbursement_amount' => 0, 'task_commission' => 0, 'task_empty_package_fee' => 0];
$items = [];
$error = $this->syncError($sync);
if ($error === null) {
try {
foreach ($this->rows($filters) as $row) {
if ($summary['count'] >= ($page - 1) * self::PER_PAGE && count($items) < self::PER_PAGE) {
$items[] = $row;
}
$summary['count']++;
foreach (['task_payment', 'reimbursement_amount', 'task_commission', 'task_empty_package_fee'] as $key) {
$summary[$key] += self::cents($row[$key]);
}
}
} catch (\Throwable $exception) {
report($exception);
$error = '种菜报销数据暂时无法读取,请稍后重试。';
$summary = array_fill_keys(array_keys($summary), 0);
$items = [];
}
}
return [
'view' => 'business.pages.billing-transactions.planting-reimbursements',
'plantingFilters' => $filters,
'plantingWindow' => Repository::window($filters['month']),
'plantingMonths' => Repository::months(),
'plantingSummary' => $summary,
'plantingSync' => $sync,
'plantingError' => $error,
'plantingRows' => new LengthAwarePaginator($items, $summary['count'], self::PER_PAGE, $page, [
'path' => request()->url(),
'query' => request()->except('page'),
]),
];
}
}