CaiWuXiTong/tests/Feature/Business/NegativeAlertPagesTest.php
苏木 c64c6f5a54 feat: 供应商负数预警按采购一键分派 (#740)
一次点击按采购拆分任务,并用一条钉钉群消息发送全部采购独立处理链接。
Co-authored-by: 苏木 <594326199@qq.com>
Co-committed-by: 苏木 <594326199@qq.com>
2026-10-28 16:09:53 +08:00

227 lines
9.5 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 Tests\Feature\Business;
use App\Http\Controllers\Business\PayablesFinalActions;
use App\Listeners\Menu\ShowInAdmin;
use App\Models\Auth\Permission;
use App\Utilities\BusinessNavigation;
use Modules\EcommerceFinance\Repositories\SupplierManagementMirrorRepository;
use Modules\EcommerceFinance\Services\PayablesFinalRegistry;
use Modules\EcommerceFinance\Services\SupplierNegativeAlertDingTalkWorkflow;
use Modules\EcommerceFinance\Services\SupplierPayableOverrideStore;
use Tests\Feature\FeatureTestCase;
class NegativeAlertPagesTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
}
public function test_negative_alerts_is_a_payables_section_with_two_children(): void
{
$navigation = collect(BusinessNavigation::all())->keyBy('slug');
$alertSection = $navigation->get('negative-alerts');
$this->assertNotNull($alertSection);
$this->assertSame('负数预警', $alertSection['title']);
$this->assertSame(
['supplier-negative-alert', 'courier-negative-alert'],
array_column($alertSection['pages'], 'slug'),
);
$this->assertNotContains(
'supplier-negative-alert',
array_column($navigation->get('supplier-reconciliation')['pages'], 'slug'),
);
$grouped = ShowInAdmin::groupPayables(BusinessNavigation::all());
$payables = collect($grouped)->firstWhere('slug', 'payable-items');
$this->assertSame('negative-alerts', $payables['sections'][0]['slug']);
}
public function test_supplier_and_courier_negative_alert_pages_render_as_tabs(): void
{
$this->saveSupplierPayable('negative:1', '负余额供应商', '-128.50', '2026-09', [
'company' => '测试供应商有限公司',
'negative_reason' => '历史多付款',
]);
$this->saveSupplierPayable('positive:1', '正余额供应商', '88.00', '2026-09');
$this->saveSupplierPayable('formula:1', '公式命中供应商', '100.00', '2026-09', [
'should_pay' => '180.00',
'estimated_price' => '50.00',
]);
$this->saveSupplierPayable('formula:exclude', '负余额但公式为正供应商', '-100.00', '2026-09', [
'should_pay' => '-500.00',
'estimated_price' => '50.00',
]);
$supplierManagement = \Mockery::mock(SupplierManagementMirrorRepository::class);
$supplierManagement->shouldReceive('suppliersByNames')
->once()
->withArgs(static fn (array $names): bool => count($names) === 2
&& in_array('负余额供应商', $names, true)
&& in_array('公式命中供应商', $names, true))
->andReturn([
(object) ['scmname' => '负余额供应商', 'buyer' => '采购甲'],
(object) ['scmname' => '公式命中供应商', 'buyer' => '采购乙'],
]);
$this->app->instance(SupplierManagementMirrorRepository::class, $supplierManagement);
$response = $this->loginAs()
->get(route('business.pages.show', [
'section' => 'negative-alerts',
'page' => 'supplier-negative-alert',
]))
->assertOk()
->assertSeeText('供应商负数预警')
->assertSeeText('快递负数预警')
->assertSeeText('按负数预警公式筛出的终版数据')
->assertSeeText('供应商余额 - 应付余额 + 预估金额 < 0')
->assertSeeText('负余额供应商')
->assertSeeText('公式命中供应商')
->assertSeeText('-128.50')
->assertSeeText('历史多付款')
->assertSeeText('采购甲')
->assertSeeText('采购乙')
->assertDontSeeText('正余额供应商')
->assertDontSeeText('负余额但公式为正供应商');
$fields = app(PayablesFinalRegistry::class)->fields(
PayablesFinalRegistry::SECTION_SUPPLIER,
'supplier-payables-report',
);
$html = (string) $response->getContent();
preg_match_all('/<th(?:\s|>)/', $html, $headers);
// 序号 + 隐藏主体名称后的原字段 + 动态匹配的采购 + 处理结果。
$this->assertCount(count($fields) + 2, $headers[0]);
preg_match_all('/<th[^>]*>(.*?)<\/th>/s', $html, $headerMatches);
$labels = array_map(
static fn (string $label): string => trim(html_entity_decode(strip_tags($label), ENT_QUOTES | ENT_HTML5, 'UTF-8')),
$headerMatches[1],
);
$negativeReasonIndex = array_search('负数原因', $labels, true);
$this->assertNotFalse($negativeReasonIndex);
$this->assertSame('采购', $labels[$negativeReasonIndex + 1]);
$this->assertSame('处理结果', $labels[$negativeReasonIndex + 2]);
$this->assertStringContainsString('待采购处理', $html);
$this->assertStringNotContainsString('供应商公司主体名称', $html);
$this->assertStringNotContainsString('测试供应商有限公司', $html);
preg_match_all('/<th[^>]*data-fixed-column="([^"]+)"/', $html, $fixedHeaders);
$this->assertSame(
['index', 'summary_month', 'checker', 'settlement_method', 'supplier'],
$fixedHeaders[1],
);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'negative-alerts',
'page' => 'courier-negative-alert',
]))
->assertOk()
->assertSeeText('供应商负数预警')
->assertSeeText('快递负数预警')
->assertSeeText('快递负数判定口径待确认')
->assertSeeText('待接入');
}
public function test_supplier_negative_alert_filters_by_month_and_supplier(): void
{
$this->saveSupplierPayable('negative:sep', '九月负余额供应商', '-99.00', '2026-09');
$this->saveSupplierPayable('negative:aug', '八月负余额供应商', '-66.00', '2026-08');
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'negative-alerts',
'page' => 'supplier-negative-alert',
'month' => '2026-08',
'supplier' => '八月负余额',
]))
->assertOk()
->assertSeeText('八月负余额供应商')
->assertSeeText('-66.00')
->assertDontSeeText('九月负余额供应商');
}
public function test_supplier_negative_alert_filters_by_checker_settlement_and_purchaser(): void
{
$this->saveSupplierPayable('filter:target', '目标供应商', '-50.00', '2026-09', [
'checker' => '李萌',
'settlement_method' => '订单合同',
]);
$this->saveSupplierPayable('filter:other', '其他供应商', '-60.00', '2026-09', [
'checker' => '陈亲园',
'settlement_method' => '终止合作',
]);
$supplierManagement = \Mockery::mock(SupplierManagementMirrorRepository::class);
$supplierManagement->shouldReceive('suppliersByNames')
->andReturn([
(object) ['scmname' => '目标供应商', 'buyer' => '采购甲'],
(object) ['scmname' => '其他供应商', 'buyer' => '采购乙'],
]);
$this->app->instance(SupplierManagementMirrorRepository::class, $supplierManagement);
app(SupplierNegativeAlertDingTalkWorkflow::class)->saveConfiguration(
'https://oapi.dingtalk.com/robot/send?access_token=test-token',
'SEC-test',
'财务&采购沟通群',
);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'negative-alerts',
'page' => 'supplier-negative-alert',
]))
->assertOk()
->assertSeeText('一键发送全部采购(2 人)');
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'negative-alerts',
'page' => 'supplier-negative-alert',
'checker' => '李萌',
'settlement_method' => '订单合同',
'purchaser' => '采购甲',
]))
->assertOk()
->assertSeeText('目标供应商')
->assertSeeText('采购甲')
->assertSeeText('发送给 采购甲(1 家)')
->assertDontSeeText('其他供应商');
}
public function test_supplier_negative_alert_route_no_longer_renders_under_supplier_reconciliation(): void
{
$this->withExceptionHandling()
->loginAs()
->get(route('business.pages.show', [
'section' => 'supplier-reconciliation',
'page' => 'supplier-negative-alert',
]))
->assertRedirect();
}
private function saveSupplierPayable(
string $key,
string $supplier,
string $supplierBalance,
string $month,
array $extra = [],
): void {
app(SupplierPayableOverrideStore::class)->save(
PayablesFinalActions::DATASET_PREFIX . 'supplier:supplier-payables-report',
$key,
$extra + [
'summary_month' => $month,
'supplier' => $supplier,
'supplier_balance' => $supplierBalance,
],
(int) $this->company->id,
(int) $this->user->id,
);
}
}