## 任务占用与边界 - 任务名称:附加费类型字典自维护 - 实际开发者:zhoushengchen - 目标结果:在快递附加费报价维护页支持附加费类型的新增、启用/停用;新增报价仅可选择启用类型,历史报价保留原类型。 - 预计修改路径:附加费类型 Repository、Model、Controller、报价 Repository/Controller、页面、管理路由、类型表迁移和专项测试。 - 将写入的公共契约:business.courier-surcharge.types.store、business.courier-surcharge.types.status、business_courier_surcharge_types。 - 仅复用、不修改的公共契约:business.pages.show、business.courier-surcharge.store。 - 明确不修改的范围:上游数据库、业务镜像、七牛、生产配置和无关页面。 ## 开工碰撞检查 - [x] 已检查最新 main 和所有远程任务分支 - [x] 已运行 scripts/codex-task-preflight.ps1 - [x] 未发现与其他远程任务的实际写入重叠 ## 实际改动 - 新增附加费类型字典及公司范围隔离。 - 新增类型管理弹窗、创建接口、启用/停用接口。 - 报价表单和后端校验动态读取启用类型;筛选仍可查看全部类型。 - 默认初始化原有 11 种类型,并增加“进仓费”。 - 增加幂等增量迁移,补齐已执行建表迁移环境中的“进仓费”。 ## 验证证据 - tests/Feature/Business/CourierSurchargePageTest.php:6 个用例、60 个断言通过。 - 相关 PHP 文件语法检查通过。 - business.courier-surcharge 路由共 5 条,路由检查通过。 - git diff --check 通过。 - 本地共享系统库现状:类型表已存在,当前公司有 11 个旧类型;增量迁移待执行。 ## 风险检查 - 未提交 .env、密码、Token、私钥、真实财务数据、生产日志或构建产物。 - 前端遵循 DESIGN.md 的 VIP 黑金令牌。 - 数据库影响:仅新增类型表(若环境尚未建表)或幂等补充“进仓费”,不删除、不覆盖已有类型。 - 回滚:类型字典不做物理删除;迁移 down 保持空操作以保护业务引用。 - 已获得本次“合并 PR 并执行类型表迁移”的明确授权;迁移目标为本机当前连接的团队系统数据库,不涉及生产部署。 ## 合并前复查 - [x] 已重新 fetch 远程引用并再次运行碰撞检查 - [x] 已逐文件审核 origin/main...任务分支完整 diff - [ ] PR 创建后移除 WIP 并执行 Squash merge - [ ] 合并后验证 main、执行迁移并验证页面 Reviewed-on: http://git.1080p.hk/WuYongJie/CaiWuXiTong/pulls/98
221 lines
9.1 KiB
PHP
221 lines
9.1 KiB
PHP
<?php
|
||
|
||
namespace Tests\Feature\Business;
|
||
|
||
use App\Models\Auth\Permission;
|
||
use App\Models\Business\CourierSurchargeType;
|
||
use App\Models\Business\CourierSurchargeQuote;
|
||
use App\Business\CourierSurchargeTypeRepository;
|
||
use Tests\Feature\FeatureTestCase;
|
||
|
||
class CourierSurchargePageTest extends FeatureTestCase
|
||
{
|
||
public function test_courier_surcharge_page_renders_persisted_quote_rules(): void
|
||
{
|
||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
$this->createQuote();
|
||
|
||
$this->loginAs()
|
||
->get(route('business.pages.show', [
|
||
'section' => 'courier-payables',
|
||
'page' => 'courier-surcharge',
|
||
]))
|
||
->assertOk()
|
||
->assertSeeText('附加费报价维护')
|
||
->assertSeeText('正式维护数据')
|
||
->assertSeeText('报价规则列表')
|
||
->assertSeeText('管理附加费类型')
|
||
->assertSeeText('类型列表')
|
||
->assertSeeText('进仓费')
|
||
->assertSeeText('新增报价规则')
|
||
->assertSeeText('结算方式')
|
||
->assertSeeText('计价标准')
|
||
->assertSeeText('生效条件')
|
||
->assertSeeText('快递归属')
|
||
->assertSeeText('自有快递')
|
||
->assertSeeText('代发仓')
|
||
->assertSeeText('对账人')
|
||
->assertSeeText('张会计')
|
||
->assertSeeText('附加费类型(可多选)')
|
||
->assertSee('name="types[]"', false)
|
||
->assertSeeInOrder(['<th>快递名称</th>', '<th>快递归属</th>', '<th>对账人</th>'], false)
|
||
->assertSeeText('曹县德邦')
|
||
->assertSeeText('超长费')
|
||
->assertSeeText('按尺寸/最长边')
|
||
->assertSeeText('8 元/单')
|
||
->assertSeeText('已启用')
|
||
->assertSeeText('后续结算页面将按启用中的规则计算');
|
||
}
|
||
|
||
public function test_courier_surcharge_type_can_be_created_and_duplicate_is_rejected(): void
|
||
{
|
||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
$this->loginAs();
|
||
|
||
$this->post(route('business.courier-surcharge.types.store'), ['name' => '偏远地区费'])
|
||
->assertRedirect();
|
||
|
||
$this->assertDatabaseHas('business_courier_surcharge_types', [
|
||
'company_id' => company_id(),
|
||
'name' => '偏远地区费',
|
||
'status' => CourierSurchargeType::STATUS_ACTIVE,
|
||
]);
|
||
|
||
$this->withExceptionHandling()
|
||
->from(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-surcharge']))
|
||
->post(route('business.courier-surcharge.types.store'), ['name' => '偏远地区费'])
|
||
->assertRedirect()
|
||
->assertSessionHasErrors('name');
|
||
}
|
||
|
||
public function test_inactive_type_is_rejected_for_new_quotes_but_keeps_historical_value(): void
|
||
{
|
||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
$this->loginAs();
|
||
|
||
$type = app(CourierSurchargeTypeRepository::class)->all()->firstWhere('name', '超长费');
|
||
$this->assertNotNull($type);
|
||
$quote = $this->createQuote();
|
||
|
||
$this->post(route('business.courier-surcharge.types.status', ['type' => $type->id]), [
|
||
'status' => CourierSurchargeType::STATUS_INACTIVE,
|
||
])->assertRedirect();
|
||
|
||
$this->withExceptionHandling()->post(route('business.courier-surcharge.store'), [
|
||
'courier' => '停用类型测试快递',
|
||
'scope' => '自有快递',
|
||
'reconciler' => '张会计',
|
||
'types' => ['超长费'],
|
||
'mode' => '固定单价',
|
||
'standard' => '1 元/单',
|
||
'condition' => '测试',
|
||
'effective_at' => '2026-09-02',
|
||
])->assertSessionHasErrors('types.0');
|
||
|
||
$this->get(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-surcharge']))
|
||
->assertOk()
|
||
->assertSeeText($quote->courier)
|
||
->assertSeeText('超长费')
|
||
->assertSeeText('管理附加费类型');
|
||
|
||
$page = $this->get(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-surcharge']))
|
||
->assertOk();
|
||
$this->assertStringNotContainsString('value="超长费"><span>超长费</span>', $page->getContent());
|
||
}
|
||
|
||
public function test_type_status_endpoint_rejects_another_company_type(): void
|
||
{
|
||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
$otherType = CourierSurchargeType::withoutGlobalScopes()->create([
|
||
'company_id' => company_id() + 999,
|
||
'name' => '其他公司类型',
|
||
'status' => CourierSurchargeType::STATUS_ACTIVE,
|
||
'sort_order' => 99,
|
||
]);
|
||
|
||
$this->loginAs();
|
||
$this->withExceptionHandling()->post(route('business.courier-surcharge.types.status', ['type' => $otherType->id]), [
|
||
'status' => CourierSurchargeType::STATUS_INACTIVE,
|
||
])->assertNotFound();
|
||
}
|
||
|
||
public function test_courier_surcharge_page_filters_persisted_rules(): void
|
||
{
|
||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
$this->createQuote();
|
||
CourierSurchargeQuote::query()->create([
|
||
'company_id' => company_id(),
|
||
'courier' => '常州松宇货运',
|
||
'scope' => '代发仓',
|
||
'reconciler' => '李会计',
|
||
'types' => ['送货费'],
|
||
'mode' => '按体积',
|
||
'standard' => '体积 ≤ 10 m³:50 元/单',
|
||
'condition' => '计费体积不超过 10 m³',
|
||
'effective_at' => '2026-09-02',
|
||
'status' => CourierSurchargeQuote::STATUS_ACTIVE,
|
||
'remark' => null,
|
||
]);
|
||
|
||
$this->loginAs()
|
||
->get(route('business.pages.show', [
|
||
'section' => 'courier-payables',
|
||
'page' => 'courier-surcharge',
|
||
'courier' => '常州松宇货运',
|
||
'scope' => '代发仓',
|
||
'reconciler' => '李会计',
|
||
'status' => '已启用',
|
||
]))
|
||
->assertOk()
|
||
->assertSeeText('常州松宇货运')
|
||
->assertSeeText('送货费')
|
||
->assertSeeText('代发仓')
|
||
->assertSeeText('李会计')
|
||
->assertSeeText('当前规则')
|
||
->assertDontSee('<td>曹县德邦</td>', false)
|
||
->assertDontSee('<td>霸州百世快运-雅柏</td>', false);
|
||
}
|
||
|
||
public function test_courier_surcharge_store_update_and_status_are_persistent(): void
|
||
{
|
||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
$this->loginAs();
|
||
|
||
$payload = [
|
||
'courier' => '测试快递',
|
||
'scope' => '代发仓',
|
||
'reconciler' => '李会计',
|
||
'types' => ['拦截费', '改地址'],
|
||
'mode' => '固定单价',
|
||
'standard' => '12 元/单',
|
||
'condition' => '拦截成功',
|
||
'effective_at' => '2026-09-02',
|
||
'remark' => '接口保存测试',
|
||
];
|
||
|
||
$this->post(route('business.courier-surcharge.store'), $payload)
|
||
->assertRedirect(route('business.pages.show', [
|
||
'company_id' => company_id(), 'section' => 'courier-payables', 'page' => 'courier-surcharge',
|
||
]));
|
||
|
||
$quote = CourierSurchargeQuote::query()->where('courier', '测试快递')->firstOrFail();
|
||
$this->assertSame(['拦截费', '改地址'], $quote->types);
|
||
$this->assertSame(CourierSurchargeQuote::STATUS_ACTIVE, $quote->status);
|
||
$this->assertSame((int) user_id(), (int) $quote->created_by);
|
||
$this->assertSame((int) user_id(), (int) $quote->updated_by);
|
||
|
||
$this->patch(route('business.courier-surcharge.update', ['quote' => $quote->id]), [
|
||
...$payload,
|
||
'courier' => '测试快递(已修改)',
|
||
'types' => ['保费'],
|
||
])->assertRedirect();
|
||
|
||
$quote->refresh();
|
||
$this->assertSame('测试快递(已修改)', $quote->courier);
|
||
$this->assertSame(['保费'], $quote->types);
|
||
|
||
$this->post(route('business.courier-surcharge.status', ['quote' => $quote->id]), [
|
||
'status' => CourierSurchargeQuote::STATUS_INACTIVE,
|
||
])->assertRedirect();
|
||
|
||
$this->assertSame(CourierSurchargeQuote::STATUS_INACTIVE, $quote->fresh()->status);
|
||
}
|
||
|
||
private function createQuote(): CourierSurchargeQuote
|
||
{
|
||
return CourierSurchargeQuote::query()->create([
|
||
'company_id' => company_id(),
|
||
'courier' => '曹县德邦',
|
||
'scope' => '自有快递',
|
||
'reconciler' => '张会计',
|
||
'types' => ['超长费'],
|
||
'mode' => '按尺寸/最长边',
|
||
'standard' => '最长边 > 160 cm:8 元/单',
|
||
'condition' => '最长边超过 160 cm',
|
||
'effective_at' => '2026-09-01',
|
||
'status' => CourierSurchargeQuote::STATUS_ACTIVE,
|
||
'remark' => '大件渠道标准',
|
||
]);
|
||
}
|
||
}
|