财务确认(2026-10-20):种菜损耗是查种菜报销的单有没有真实发出有价值的货—— - 发货数据中交易单号下出现信誉订单虚拟条码或透明挂钩(含条形码 GG 开头)即整单不扣款; - 任务备注/卖家备注含拍A发B/拍A发A 的不扣款,且无论是否匹配到发货都从种菜报销中挑出发运营; - “导出拍A发B(发运营)”直接按「种菜报销」页字段输出;其余真实发货的单按成本价×数量+运费扣款。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VANWYsNUbu2piMtCKymoyd
128 lines
6.7 KiB
PHP
128 lines
6.7 KiB
PHP
<?php
|
||
|
||
namespace Tests\Feature\Business;
|
||
|
||
use App\Models\Auth\Permission;
|
||
use Modules\EcommerceFinance\Repositories\PlantingReimbursementRepository;
|
||
use Modules\EcommerceFinance\Services\PlantingLossCalculator as Calculator;
|
||
use Modules\EcommerceFinance\View\PlantingLossPageData;
|
||
use Modules\EcommerceFinance\View\PlantingReimbursementPageData;
|
||
use Tests\Feature\FeatureTestCase;
|
||
|
||
class PlantingWorkOrderPagesTest extends FeatureTestCase
|
||
{
|
||
private function row(array $patch = []): array
|
||
{
|
||
$row = array_fill_keys(array_keys(PlantingLossPageData::COLUMNS + PlantingReimbursementPageData::COLUMNS), '');
|
||
|
||
return array_replace($row, [
|
||
'completed_at' => '2026-09-02 11:47:53', 'work_order_no' => 'GD565967381', 'status' => '已完成',
|
||
'shop_name' => '天猫-卓禾家具旗舰店', 'online_order_no' => '3316392626048007769', 'task_remark' => 'CCDD',
|
||
'seller_remark' => 'AABB', 'actual_paid_amount' => '37.79', 'task_payment' => '37.79', 'task_freight' => '0.00',
|
||
'task_commission' => '2.00', 'jingxi_price_diff' => '0.00', 'task_deduction' => '0.00',
|
||
'task_empty_package_fee' => '0.00', 'reimbursement_amount' => '39.79', 'payee_name' => '黄文梳',
|
||
'loss_result' => Calculator::RESULT_DEDUCT, 'loss_basis' => '已匹配发货,备注无拍A发B/拍A发A',
|
||
'shipped_amount' => '40.60', 'shipped_freight' => '3.20', 'express_no' => 'YT001',
|
||
'deduction_amount' => '207.40', 'deduction_detail' => "DNZ-01 × 2 × 100.5 = 201.00\n运费 6.40", 'cost_missing' => false,
|
||
], $patch);
|
||
}
|
||
|
||
private function mockData(array $rows): void
|
||
{
|
||
$repository = \Mockery::mock(PlantingReimbursementRepository::class);
|
||
$repository->shouldReceive('syncStatus')->andReturn(['code' => 'ready', 'last_succeeded_at' => '2026-10-19 13:00:00']);
|
||
$repository->shouldReceive('rows')->andReturn($rows);
|
||
$this->app->instance(PlantingReimbursementRepository::class, $repository);
|
||
|
||
$calculator = \Mockery::mock(Calculator::class);
|
||
$calculator->shouldReceive('rows')->andReturn($rows);
|
||
$calculator->shouldReceive('shippingStatus')->andReturn(['code' => 'succeeded', 'last_succeeded_at' => '2026-10-19 12:00:00']);
|
||
$this->app->instance(Calculator::class, $calculator);
|
||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
}
|
||
|
||
private function url(string $page, array $query = []): string
|
||
{
|
||
return route('business.pages.show', ['company_id' => $this->company->id, 'section' => 'planting-data', 'page' => $page])
|
||
. ($query ? '?' . http_build_query($query) : '');
|
||
}
|
||
|
||
public function test_reimbursement_page_lists_month_rows_and_totals(): void
|
||
{
|
||
$this->mockData([$this->row(), $this->row(['work_order_no' => 'GD-2', 'shop_name' => '拼多多-归来舍下', 'reimbursement_amount' => '0.56', 'task_payment' => '0.06'])]);
|
||
|
||
$this->loginAs()->get($this->url('planting-reimbursements', ['month' => '2026-09']))
|
||
->assertOk()
|
||
->assertSee('种菜报销')
|
||
->assertSee('GD565967381')
|
||
->assertSee('GD-2')
|
||
->assertSee('40.35')
|
||
->assertSee('2026-09-02 00:00');
|
||
|
||
$this->loginAs()->get($this->url('planting-reimbursements', ['month' => '2026-09', 'shop_name' => '拼多多']))
|
||
->assertOk()->assertSee('GD-2')->assertDontSee('GD565967381');
|
||
|
||
$this->withExceptionHandling()->loginAs()->get($this->url('planting-reimbursements', ['month' => '2026-08']))->assertStatus(422);
|
||
}
|
||
|
||
public function test_loss_page_defaults_to_deduct_rows_and_splits_exempt(): void
|
||
{
|
||
$this->mockData([
|
||
$this->row(),
|
||
$this->row(['work_order_no' => 'GD-EXEMPT', 'task_remark' => '拍A发B+GG003', 'loss_result' => Calculator::RESULT_EXEMPT, 'loss_basis' => '备注含“拍A发B”']),
|
||
$this->row(['work_order_no' => 'GD-NONE', 'loss_result' => Calculator::RESULT_UNMATCHED, 'shipped_amount' => null]),
|
||
]);
|
||
|
||
$this->loginAs()->get($this->url('planting-loss', ['month' => '2026-09']))
|
||
->assertOk()
|
||
->assertSee('种菜损耗')
|
||
->assertSee('GD565967381')
|
||
->assertDontSee('GD-EXEMPT')
|
||
->assertDontSee('GD-NONE')
|
||
->assertSee('2026-08-01')
|
||
->assertSee('207.40')
|
||
->assertSee('DNZ-01 × 2 × 100.5 = 201.00');
|
||
|
||
$this->loginAs()->get($this->url('planting-loss', ['month' => '2026-09', 'result' => Calculator::RESULT_EXEMPT]))
|
||
->assertOk()->assertSee('GD-EXEMPT')->assertDontSee('GD565967381');
|
||
|
||
$this->withExceptionHandling()->loginAs()->get($this->url('planting-loss', ['month' => '2026-09', 'result' => 'x']))->assertStatus(422);
|
||
}
|
||
|
||
public function test_loss_export_for_operations_contains_only_exempt_rows(): void
|
||
{
|
||
$this->mockData([
|
||
$this->row(),
|
||
$this->row(['work_order_no' => 'GD-EXEMPT', 'task_remark' => '拍A发B', 'loss_result' => Calculator::RESULT_EXEMPT]),
|
||
]);
|
||
|
||
$response = $this->loginAs()->get(route('business.planting-data.planting-loss.export', [
|
||
'company_id' => $this->company->id, 'month' => '2026-09', 'result' => Calculator::RESULT_EXEMPT,
|
||
'columns' => 'work_order_no,online_order_no,task_remark,loss_result',
|
||
]));
|
||
|
||
$response->assertOk();
|
||
$csv = $response->streamedContent();
|
||
$this->assertStringContainsString('工单编号,线上订单号,任务备注,损耗判定', $csv);
|
||
$this->assertStringContainsString("GD-EXEMPT,'3316392626048007769,拍A发B", $csv);
|
||
$this->assertStringNotContainsString('GD565967381', $csv);
|
||
|
||
// 发运营:按种菜报销字段导出,忽略损耗页的列设置。
|
||
$operations = $this->loginAs()->get(route('business.planting-data.planting-loss.export', [
|
||
'company_id' => $this->company->id, 'month' => '2026-09', 'result' => Calculator::RESULT_EXEMPT, 'format' => 'reimbursement',
|
||
]));
|
||
$operations->assertOk();
|
||
$opsCsv = $operations->streamedContent();
|
||
$this->assertStringContainsString(implode(',', array_values(PlantingReimbursementPageData::COLUMNS)), $opsCsv);
|
||
$this->assertStringContainsString('GD-EXEMPT', $opsCsv);
|
||
$this->assertStringNotContainsString('GD565967381', $opsCsv);
|
||
$this->assertStringNotContainsString('损耗判定', $opsCsv);
|
||
|
||
$reimbursements = $this->loginAs()->get(route('business.planting-data.planting-reimbursements.export', [
|
||
'company_id' => $this->company->id, 'month' => '2026-09',
|
||
]));
|
||
$reimbursements->assertOk();
|
||
$this->assertStringContainsString('最终报销金额', $reimbursements->streamedContent());
|
||
}
|
||
}
|