移除对账
This commit is contained in:
@@ -1,191 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\CustomerLevelModel;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\ProductPriceModel;
|
||||
use App\Models\ReconciliationItemModel;
|
||||
use App\Models\ReconciliationModel;
|
||||
use App\Models\SettlementModel;
|
||||
use App\Models\StoreModel;
|
||||
use App\Models\StoreOrderModel;
|
||||
use App\Models\SupplierModel;
|
||||
use App\Models\UserModel;
|
||||
|
||||
/**
|
||||
* 财务对账:明细构建(已完成订单直读,品类/供应商筛选)、D4 修改后差额与头汇总重算、D8 标记、D9 结算
|
||||
* publish = 订货金额;actual = 采购成本(称重>0 ? 称重×单价 : 数量×单价,单价 = 成本/包规)
|
||||
*/
|
||||
class ReconciliationTest extends ProcurementTestCase
|
||||
{
|
||||
/**
|
||||
* 构造已完成订单链路:2 门店下单(2/3 件,等级价 10.00;成本 8,包规 1斤 → 单价 8.00),订单置为已完成
|
||||
* 预期:publish 20/30,actual 16/24,diff 4/6
|
||||
*
|
||||
* @return array{0: array<int, StoreModel>, 1: SupplierModel}
|
||||
*/
|
||||
private function buildCompletedOrders(): array
|
||||
{
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
$supplier = SupplierModel::factory()->create();
|
||||
$product = ProductModel::factory()->create([
|
||||
'status' => ProductModel::STATUS_ON,
|
||||
'supplier_id' => $supplier->id,
|
||||
'cost_price' => 8,
|
||||
'spec' => '1斤',
|
||||
]);
|
||||
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => '10.00']);
|
||||
|
||||
$stores = [];
|
||||
foreach ([2, 3] as $qty) {
|
||||
$store = StoreModel::factory()->create(['level_id' => $level->id]);
|
||||
$stores[] = $store;
|
||||
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
|
||||
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => $qty]]])
|
||||
->assertJsonPath('success', true);
|
||||
}
|
||||
|
||||
// 订单完成(对账数据源为已完成订单的订货明细)
|
||||
StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_COMPLETED]);
|
||||
|
||||
return [$stores, $supplier];
|
||||
}
|
||||
|
||||
private function createRecon(array $extra = []): int
|
||||
{
|
||||
$response = $this->postJson('/recon/list', array_merge([
|
||||
'title' => '测试对账',
|
||||
'period_start' => now()->toDateString(),
|
||||
'period_end' => now()->toDateString(),
|
||||
], $extra));
|
||||
$response->assertJsonPath('success', true);
|
||||
|
||||
return (int) $response->json('data.id');
|
||||
}
|
||||
|
||||
/** 构建明细:publish=订货金额,actual=采购成本(数量×单价),diff=publish-actual,头汇总回写 */
|
||||
public function test_build_creates_reconciliation_items(): void
|
||||
{
|
||||
[$stores] = $this->buildCompletedOrders();
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$reconId = $this->createRecon();
|
||||
$this->postJson("/recon/list/{$reconId}/build")->assertJsonPath('success', true);
|
||||
|
||||
$items = ReconciliationItemModel::where('recon_id', $reconId)->get();
|
||||
$this->assertCount(2, $items, '两门店已完成订单 → 两条对账明细');
|
||||
|
||||
$byStore = $items->keyBy('store_id');
|
||||
$this->assertSame('20.00', (string) $byStore[$stores[0]->id]->publish_amount, '订货金额 2×10');
|
||||
$this->assertSame('16.00', (string) $byStore[$stores[0]->id]->actual_amount, '采购成本 2×8');
|
||||
$this->assertSame('4.00', (string) $byStore[$stores[0]->id]->diff_amount);
|
||||
$this->assertSame('30.00', (string) $byStore[$stores[1]->id]->publish_amount);
|
||||
$this->assertSame('24.00', (string) $byStore[$stores[1]->id]->actual_amount);
|
||||
|
||||
$recon = ReconciliationModel::find($reconId);
|
||||
$this->assertSame('50.00', (string) $recon->publish_amount);
|
||||
$this->assertSame('40.00', (string) $recon->actual_amount);
|
||||
$this->assertSame('10.00', (string) $recon->diff_amount);
|
||||
$this->assertSame(ReconciliationModel::STATUS_WORKING, $recon->status);
|
||||
}
|
||||
|
||||
/** 未完成订单不计入对账 */
|
||||
public function test_build_excludes_unfinished_orders(): void
|
||||
{
|
||||
[$stores] = $this->buildCompletedOrders();
|
||||
// 第二家门店订单回退为配送中 → 仅第一家进入对账
|
||||
StoreOrderModel::where('store_id', $stores[1]->id)
|
||||
->update(['status' => StoreOrderModel::STATUS_DISTRIBUTION]);
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$reconId = $this->createRecon();
|
||||
$this->postJson("/recon/list/{$reconId}/build")->assertJsonPath('success', true);
|
||||
|
||||
$items = ReconciliationItemModel::where('recon_id', $reconId)->get();
|
||||
$this->assertCount(1, $items);
|
||||
$this->assertSame($stores[0]->id, $items->first()->store_id);
|
||||
}
|
||||
|
||||
/** 供应商筛选:仅拉取该供应商的订单数据 */
|
||||
public function test_build_filters_by_supplier(): void
|
||||
{
|
||||
[, $supplier] = $this->buildCompletedOrders();
|
||||
$this->actingAsSysUser();
|
||||
|
||||
// 无关供应商 → 无数据报错
|
||||
$other = SupplierModel::factory()->create();
|
||||
$reconId = $this->createRecon(['supplier_id' => $other->id]);
|
||||
$this->postJson("/recon/list/{$reconId}/build")->assertJsonPath('success', false);
|
||||
|
||||
// 正确供应商 → 构建成功
|
||||
$reconId2 = $this->createRecon(['supplier_id' => $supplier->id]);
|
||||
$this->postJson("/recon/list/{$reconId2}/build")->assertJsonPath('success', true);
|
||||
$this->assertSame(2, ReconciliationItemModel::where('recon_id', $reconId2)->count());
|
||||
}
|
||||
|
||||
/** D4 修改明细:自动重算本行 diff 与对账单头汇总 */
|
||||
public function test_update_item_recalculates_diff_and_header(): void
|
||||
{
|
||||
$this->buildCompletedOrders();
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$reconId = $this->createRecon();
|
||||
$this->postJson("/recon/list/{$reconId}/build");
|
||||
|
||||
$item = ReconciliationItemModel::where('recon_id', $reconId)->orderBy('id')->first();
|
||||
$this->putJson("/recon/item/{$item->id}", ['actual_amount' => '25.00'])
|
||||
->assertJsonPath('success', true);
|
||||
|
||||
$item = $item->fresh();
|
||||
$this->assertSame('-5.00', (string) $item->diff_amount, '20.00 - 25.00');
|
||||
|
||||
$recon = ReconciliationModel::find($reconId);
|
||||
$this->assertSame('49.00', (string) $recon->actual_amount, '25 + 24');
|
||||
$this->assertSame('1.00', (string) $recon->diff_amount, '50 - 49');
|
||||
}
|
||||
|
||||
/** D8 对账状态标记翻转 */
|
||||
public function test_toggle_reconciled_flag(): void
|
||||
{
|
||||
$this->buildCompletedOrders();
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$reconId = $this->createRecon();
|
||||
$this->postJson("/recon/list/{$reconId}/build");
|
||||
$item = ReconciliationItemModel::where('recon_id', $reconId)->first();
|
||||
$this->assertSame(0, $item->is_reconciled);
|
||||
|
||||
$this->putJson("/recon/item/{$item->id}/toggle")->assertJsonPath('success', true);
|
||||
$this->assertSame(1, $item->fresh()->is_reconciled);
|
||||
|
||||
$this->putJson("/recon/item/{$item->id}/toggle")->assertJsonPath('success', true);
|
||||
$this->assertSame(0, $item->fresh()->is_reconciled);
|
||||
}
|
||||
|
||||
/** D9 结算:按门店生成结算表,对账单转为已结算且不可重复结算 */
|
||||
public function test_settle_creates_settlements_per_store(): void
|
||||
{
|
||||
[$stores] = $this->buildCompletedOrders();
|
||||
$this->actingAsSysUser();
|
||||
|
||||
$reconId = $this->createRecon();
|
||||
$this->postJson("/recon/list/{$reconId}/build");
|
||||
|
||||
$this->postJson("/recon/list/{$reconId}/settle")->assertJsonPath('success', true);
|
||||
|
||||
$settlements = SettlementModel::where('recon_id', $reconId)->get();
|
||||
$this->assertCount(2, $settlements, '按门店各生成一张结算表');
|
||||
|
||||
$byStore = $settlements->keyBy('store_id');
|
||||
$this->assertSame('20.00', (string) $byStore[$stores[0]->id]->total_amount);
|
||||
$this->assertSame('16.00', (string) $byStore[$stores[0]->id]->actual_amount);
|
||||
$this->assertSame('4.00', (string) $byStore[$stores[0]->id]->diff_amount);
|
||||
$this->assertStringStartsWith('JS', $byStore[$stores[0]->id]->settlement_no);
|
||||
|
||||
$this->assertSame(ReconciliationModel::STATUS_SETTLED, ReconciliationModel::find($reconId)->status);
|
||||
|
||||
// 已结算不可重复结算
|
||||
$this->postJson("/recon/list/{$reconId}/settle")->assertJsonPath('success', false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user