742 lines
30 KiB
PHP
742 lines
30 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Business;
|
|
|
|
use App\Business\CompanyPlan\CompanyPlanRepository;
|
|
use App\Business\ServiceCompanyReconciliation\ServiceCompanyReconciliationRepository;
|
|
use App\Models\Auth\Permission;
|
|
use App\Models\Auth\Role;
|
|
use App\Models\Auth\User;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Modules\EcommerceFinance\Repositories\BusinessInformationMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\StoreReportRepository;
|
|
use Tests\Feature\FeatureTestCase;
|
|
|
|
class ServiceCompanyReconciliationTest extends FeatureTestCase
|
|
{
|
|
private string $companyPlanPath;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->companyPlanPath = storage_path(
|
|
'framework/testing/service-company-plan-' . Str::uuid() . '.sqlite',
|
|
);
|
|
config(['company_plan.database_path' => $this->companyPlanPath]);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
@unlink($this->companyPlanPath);
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_overview_imports_company_plan_rows_whose_type_contains_service_company(): void
|
|
{
|
|
$pdo = app(CompanyPlanRepository::class)->pdo();
|
|
$insert = $pdo->prepare(<<<'SQL'
|
|
INSERT INTO company_plan_rows (
|
|
company_id, sequence_no, company_name, company_type, taxpayer_type,
|
|
business_registration_type, unified_social_credit_code, company_status, created_at, updated_at
|
|
) VALUES (
|
|
:company_id, :sequence_no, :company_name, :company_type, :taxpayer_type,
|
|
:business_registration_type, :credit_code, :company_status, :created_at, :updated_at
|
|
)
|
|
SQL);
|
|
$now = now()->toDateTimeString();
|
|
foreach ([
|
|
[1, '上海测试科技有限公司', '科技有限公司', '一般纳税人', '查账征收', '91310000TESTTECH01'],
|
|
[2, '常州测试仓储服务有限公司', '仓储服务公司', '小规模纳税人', '查账征收', '91320400TESTWAREHOUSE01'],
|
|
[3, '常州测试客服服务有限公司', '客服服务公司', '小规模纳税人', '个人独资企业', '91320400TESTCUSTOMER06'],
|
|
[4, '常州测试运营服务有限公司', '运营服务公司', '小规模纳税人', '核定', '91320400TESTOPS01'],
|
|
] as [$sequence, $name, $type, $taxType, $businessType, $creditCode]) {
|
|
$insert->execute([
|
|
':company_id' => $this->company->id,
|
|
':sequence_no' => $sequence,
|
|
':company_name' => $name,
|
|
':company_type' => $type,
|
|
':taxpayer_type' => $taxType,
|
|
':business_registration_type' => $businessType,
|
|
':credit_code' => $creditCode,
|
|
':company_status' => '正常',
|
|
':created_at' => $now,
|
|
':updated_at' => $now,
|
|
]);
|
|
}
|
|
|
|
$this->loginAs()
|
|
->get(route('business.service-company-reconciliation.index'))
|
|
->assertOk()
|
|
->assertSee('class="svc-table svc-table--companies"', false)
|
|
->assertSeeText('常州测试仓储服务有限公司')
|
|
->assertSeeText('常州测试客服服务有限公司')
|
|
->assertSeeText('常州测试运营服务有限公司')
|
|
->assertSeeText('个人独资企业')
|
|
->assertSeeText('核定')
|
|
->assertSeeInOrder([
|
|
'<th>公司名称</th>',
|
|
'<th>类型</th>',
|
|
'<th>税务类型</th>',
|
|
'<th>工商类型</th>',
|
|
], false)
|
|
->assertSee('<th class="is-number">结算比例</th>', false)
|
|
->assertDontSee('<th class="is-number">结算基数</th>', false)
|
|
->assertSeeText('1.5%')
|
|
->assertDontSeeText('上海测试科技有限公司');
|
|
|
|
$types = DB::connection('business_mirror')
|
|
->table('svc_service_companies')
|
|
->where('status', 'active')
|
|
->pluck('service_type_code')
|
|
->sort()
|
|
->values()
|
|
->all();
|
|
$this->assertSame(['customer_service', 'operations', 'warehouse'], $types);
|
|
$this->assertSame(
|
|
3,
|
|
DB::connection('business_mirror')
|
|
->table('svc_service_companies')
|
|
->where('source', 'company_plan')
|
|
->count(),
|
|
);
|
|
|
|
$pdo->prepare(
|
|
"UPDATE company_plan_rows SET company_type = '科技有限公司' WHERE company_name = ?"
|
|
)->execute(['常州测试客服服务有限公司']);
|
|
$this->get(route('business.service-company-reconciliation.index'))
|
|
->assertOk()
|
|
->assertDontSeeText('常州测试客服服务有限公司');
|
|
$this->assertSame(
|
|
'inactive',
|
|
DB::connection('business_mirror')
|
|
->table('svc_service_companies')
|
|
->where('unified_social_credit_code', '91320400TESTCUSTOMER06')
|
|
->value('status'),
|
|
);
|
|
}
|
|
|
|
public function test_service_company_and_relation_can_be_configured_from_the_real_page(): void
|
|
{
|
|
$this->seedStoreMirror();
|
|
$this->loginAs();
|
|
|
|
$this->get(route('business.service-company-reconciliation.index'))
|
|
->assertOk()
|
|
->assertSeeText('服务公司总览')
|
|
->assertSeeText('新增服务公司');
|
|
|
|
$response = $this->post(route('business.service-company-reconciliation.companies.store'), [
|
|
'company_name' => '常州测试客服服务有限公司',
|
|
'unified_social_credit_code' => '91320400TESTCUSTOMER01',
|
|
'service_type_code' => 'customer_service',
|
|
'taxpayer_type' => '小规模纳税人',
|
|
'business_registration_type' => '查账征收',
|
|
'company_status' => '正常',
|
|
'planning_target_amount' => 3000000,
|
|
'status' => 'active',
|
|
'notes' => '测试服务公司',
|
|
]);
|
|
|
|
$serviceCompanyId = (int) DB::connection('business_mirror')
|
|
->table('svc_service_companies')
|
|
->value('id');
|
|
$response->assertRedirect(route('business.service-company-reconciliation.show', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
]));
|
|
|
|
$storeCompanyKey = ServiceCompanyReconciliationRepository::companyKey(
|
|
'上海测试店铺公司有限公司',
|
|
'91310000TESTSTORE01',
|
|
);
|
|
$this->post(route('business.service-company-reconciliation.relations.store', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
]), [
|
|
'store_company_key' => $storeCompanyKey,
|
|
'base_metric' => 'settlement_income',
|
|
'fee_rate_percent' => 1.5,
|
|
'effective_from' => '2025-01-01',
|
|
'effective_to' => '',
|
|
'contract_no' => 'HT-TEST-001',
|
|
'contract_status' => 'stamped',
|
|
'status' => 'active',
|
|
])->assertSessionHasNoErrors();
|
|
$relationId = (int) DB::connection('business_mirror')
|
|
->table('svc_service_relations')
|
|
->value('id');
|
|
$updateRoute = route('business.service-company-reconciliation.relations.update', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
'relation' => $relationId,
|
|
]);
|
|
$relationPayload = [
|
|
'store_company_key' => $storeCompanyKey,
|
|
'base_metric' => 'settlement_income',
|
|
'fee_rate_percent' => 2,
|
|
'effective_from' => '2025-01-01',
|
|
'effective_to' => '',
|
|
'contract_no' => 'HT-TEST-001',
|
|
'contract_status' => 'stamped',
|
|
'status' => 'active',
|
|
];
|
|
$this->withExceptionHandling()
|
|
->patch($updateRoute, $relationPayload)
|
|
->assertSessionHasErrors('fee_rate_percent');
|
|
$this->assertSame(
|
|
1,
|
|
(int) DB::connection('business_mirror')
|
|
->table('svc_service_relations')
|
|
->where('id', $relationId)
|
|
->value('rule_version'),
|
|
);
|
|
|
|
$relationPayload['fee_rate_percent'] = 1.5;
|
|
$this->patch($updateRoute, $relationPayload)
|
|
->assertSessionHasNoErrors();
|
|
|
|
$this->assertSame(
|
|
2,
|
|
(int) DB::connection('business_mirror')
|
|
->table('svc_service_relations')
|
|
->where('id', $relationId)
|
|
->value('rule_version'),
|
|
);
|
|
$this->assertSame(
|
|
2,
|
|
DB::connection('business_mirror')
|
|
->table('svc_service_relation_versions')
|
|
->where('relation_id', $relationId)
|
|
->count(),
|
|
);
|
|
|
|
$this->get(route('business.service-company-reconciliation.show', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
'tab' => 'relations',
|
|
]))
|
|
->assertOk()
|
|
->assertSeeText('常州测试客服服务有限公司')
|
|
->assertSeeText('上海测试店铺公司有限公司')
|
|
->assertSeeText('HT-TEST-001')
|
|
->assertSeeText('1.5000%')
|
|
->assertSeeText('客服服务公司结算比例固定为 1.5%')
|
|
->assertSee('data-fixed-fee-rate="1.5"', false)
|
|
->assertSeeText('同一店铺公司 + 同一服务类型');
|
|
}
|
|
|
|
public function test_non_customer_service_company_keeps_configurable_settlement_rate(): void
|
|
{
|
|
$this->seedStoreMirror();
|
|
$this->loginAs();
|
|
$this->post(route('business.service-company-reconciliation.companies.store'), [
|
|
'company_name' => '常州测试运营服务有限公司',
|
|
'unified_social_credit_code' => '91320400TESTOPS02',
|
|
'service_type_code' => 'operations',
|
|
'company_status' => '正常',
|
|
'status' => 'active',
|
|
])->assertSessionHasNoErrors();
|
|
|
|
$serviceCompanyId = (int) DB::connection('business_mirror')
|
|
->table('svc_service_companies')
|
|
->where('unified_social_credit_code', '91320400TESTOPS02')
|
|
->value('id');
|
|
$storeCompanyKey = ServiceCompanyReconciliationRepository::companyKey(
|
|
'上海测试店铺公司有限公司',
|
|
'91310000TESTSTORE01',
|
|
);
|
|
$this->post(route('business.service-company-reconciliation.relations.store', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
]), [
|
|
'store_company_key' => $storeCompanyKey,
|
|
'base_metric' => 'settlement_income',
|
|
'fee_rate_percent' => 2,
|
|
'effective_from' => '2025-01-01',
|
|
'effective_to' => '',
|
|
'contract_no' => 'HT-TEST-OPERATIONS',
|
|
'contract_status' => 'stamped',
|
|
'status' => 'active',
|
|
])->assertSessionHasNoErrors();
|
|
|
|
$this->assertSame(
|
|
0.02,
|
|
(float) DB::connection('business_mirror')
|
|
->table('svc_service_relations')
|
|
->where('service_company_id', $serviceCompanyId)
|
|
->value('fee_rate'),
|
|
);
|
|
$this->get(route('business.service-company-reconciliation.index'))
|
|
->assertOk()
|
|
->assertSeeText('2%')
|
|
->assertDontSeeText('结算基数');
|
|
}
|
|
|
|
public function test_customer_service_excel_mapping_is_synced_from_store_company_to_relation(): void
|
|
{
|
|
$this->seedStoreMirror();
|
|
$this->loginAs();
|
|
$serviceCompanyId = $this->createServiceCompany(
|
|
'常州测试客服服务有限公司',
|
|
'91320400TESTCUSTOMER05',
|
|
);
|
|
$repository = app(ServiceCompanyReconciliationRepository::class);
|
|
$rows = [[
|
|
'service_company_name' => '常州测试客服服务有限公司',
|
|
'store_company_name' => '上海测试店铺公司有限公司',
|
|
'fee_rate' => 0.015,
|
|
'source_row' => 2,
|
|
]];
|
|
|
|
$first = $repository->syncCustomerServiceMappings(
|
|
$this->company->id,
|
|
$rows,
|
|
'2026-10-01',
|
|
'测试导入',
|
|
);
|
|
$this->assertSame(1, $first['created']);
|
|
$this->assertSame([], $first['errors']);
|
|
$relation = DB::connection('business_mirror')
|
|
->table('svc_service_relations')
|
|
->where('service_company_id', $serviceCompanyId)
|
|
->first();
|
|
$this->assertNotNull($relation);
|
|
$this->assertSame('customer_service_mapping', $relation->source);
|
|
$this->assertSame('2026-10-01', $relation->effective_from);
|
|
$this->assertSame(0.015, (float) $relation->fee_rate);
|
|
$this->assertSame(
|
|
ServiceCompanyReconciliationRepository::companyKey(
|
|
'上海测试店铺公司有限公司',
|
|
'91310000TESTSTORE01',
|
|
),
|
|
$relation->store_company_key,
|
|
);
|
|
|
|
$second = $repository->syncCustomerServiceMappings(
|
|
$this->company->id,
|
|
$rows,
|
|
'2026-10-01',
|
|
'测试导入',
|
|
);
|
|
$this->assertSame(1, $second['unchanged']);
|
|
$this->assertSame(1, DB::connection('business_mirror')->table('svc_service_relations')->count());
|
|
}
|
|
|
|
public function test_unassigned_store_with_income_is_auto_balanced_once_and_then_fixed(): void
|
|
{
|
|
$this->seedStoreMirror('2026-10');
|
|
$this->loginAs();
|
|
$firstServiceId = $this->createServiceCompany(
|
|
'常州测试客服甲公司',
|
|
'91320400TESTCUSTOMERA',
|
|
);
|
|
$secondServiceId = $this->createServiceCompany(
|
|
'常州测试客服乙公司',
|
|
'91320400TESTCUSTOMERB',
|
|
);
|
|
$mirror = DB::connection('business_mirror');
|
|
$paidGeneration = '11111111-1111-1111-1111-111111111111';
|
|
$lifecycleGeneration = '22222222-2222-2222-2222-222222222222';
|
|
foreach ([
|
|
['上海测试店铺二公司有限公司', '91310000TESTSTORE02', true],
|
|
['上海测试店铺三公司有限公司', '91310000TESTSTORE03', false],
|
|
] as [$companyName, $creditCode, $hasIncome]) {
|
|
$mirror->table('store_report_lifecycle_rows')->insert([
|
|
'generation' => $lifecycleGeneration,
|
|
'business_key' => hash('sha256', $companyName . '|2025-01-01'),
|
|
'erp_store_name' => $companyName . '店铺',
|
|
'company_name' => $companyName,
|
|
'unified_social_credit_code' => $creditCode,
|
|
'effective_at' => '2025-01-01 00:00:00',
|
|
'expires_at' => null,
|
|
'source_file' => 'test.xlsx',
|
|
'row_hash' => hash('sha256', 'lifecycle-' . $creditCode),
|
|
'imported_at' => now(),
|
|
]);
|
|
if ($hasIncome) {
|
|
$mirror->table('store_report_paid_sales_rows')->insert([
|
|
'generation' => $paidGeneration,
|
|
'store_name' => $companyName . '店铺',
|
|
'occurred_on' => '2026-10-09',
|
|
'paid_amount' => 20000,
|
|
'settlement_income' => 20000,
|
|
'order_count' => 1,
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
$this->mock(BusinessInformationMirrorRepository::class, function ($mock): void {
|
|
$mock->shouldReceive('allStores')->andReturn([['erp_store_name' => '测试店铺']]);
|
|
});
|
|
$this->mock(StoreReportRepository::class, function ($mock): void {
|
|
$mock->shouldReceive('rows')->andReturn([
|
|
[
|
|
'entity' => '上海测试店铺公司有限公司',
|
|
'previous_month_settlement_income' => 125000,
|
|
],
|
|
[
|
|
'entity' => '上海测试店铺二公司有限公司',
|
|
'previous_month_settlement_income' => 20000,
|
|
],
|
|
[
|
|
'entity' => '上海测试店铺三公司有限公司',
|
|
'previous_month_settlement_income' => 0,
|
|
],
|
|
]);
|
|
});
|
|
$repository = app(ServiceCompanyReconciliationRepository::class);
|
|
$assigned = [[
|
|
'service_company_name' => '常州测试客服甲公司',
|
|
'store_company_name' => '上海测试店铺公司有限公司',
|
|
'fee_rate' => 0.015,
|
|
'source_row' => 2,
|
|
]];
|
|
$unassigned = [
|
|
['store_company_name' => '上海测试店铺二公司有限公司', 'fee_rate' => 0.015, 'source_row' => 3],
|
|
['store_company_name' => '上海测试店铺三公司有限公司', 'fee_rate' => 0.015, 'source_row' => 4],
|
|
];
|
|
$first = $repository->syncCustomerServiceMappings(
|
|
$this->company->id,
|
|
$assigned,
|
|
'2026-10-01',
|
|
'测试导入',
|
|
$unassigned,
|
|
'2026-10',
|
|
);
|
|
$this->assertSame(1, $first['auto_assigned']);
|
|
$this->assertSame(125000.0, $first['auto_totals']['常州测试客服甲公司']);
|
|
$this->assertSame(20000.0, $first['auto_totals']['常州测试客服乙公司']);
|
|
$autoRelation = $mirror->table('svc_service_relations')
|
|
->where('store_company_name', '上海测试店铺二公司有限公司')
|
|
->first();
|
|
$this->assertSame($secondServiceId, (int) $autoRelation->service_company_id);
|
|
$this->assertSame('customer_service_auto_balance', $autoRelation->source);
|
|
$this->assertSame(
|
|
0,
|
|
$mirror->table('svc_service_relations')
|
|
->where('store_company_name', '上海测试店铺三公司有限公司')
|
|
->count(),
|
|
);
|
|
|
|
$second = $repository->syncCustomerServiceMappings(
|
|
$this->company->id,
|
|
$assigned,
|
|
'2026-10-01',
|
|
'再次导入',
|
|
$unassigned,
|
|
'2026-10',
|
|
);
|
|
$this->assertSame(0, $second['auto_assigned']);
|
|
$this->assertSame(
|
|
$secondServiceId,
|
|
(int) $mirror->table('svc_service_relations')
|
|
->where('store_company_name', '上海测试店铺二公司有限公司')
|
|
->value('service_company_id'),
|
|
);
|
|
$this->assertSame(
|
|
2,
|
|
$mirror->table('svc_service_relations')->count(),
|
|
);
|
|
|
|
$conflict = $repository->syncCustomerServiceMappings(
|
|
$this->company->id,
|
|
[[
|
|
'service_company_name' => '常州测试客服乙公司',
|
|
'store_company_name' => '上海测试店铺公司有限公司',
|
|
'fee_rate' => 0.015,
|
|
'source_row' => 2,
|
|
]],
|
|
'2026-10-01',
|
|
'错误改派',
|
|
[],
|
|
'2026-10',
|
|
);
|
|
$this->assertNotSame([], $conflict['errors']);
|
|
$this->assertSame(
|
|
$firstServiceId,
|
|
(int) $mirror->table('svc_service_relations')
|
|
->where('store_company_name', '上海测试店铺公司有限公司')
|
|
->value('service_company_id'),
|
|
);
|
|
|
|
$this->mock(StoreReportRepository::class, function ($mock): void {
|
|
$mock->shouldReceive('rows')->andReturn([[
|
|
'entity' => '上海测试店铺二公司有限公司',
|
|
'previous_month_settlement_income' => 0,
|
|
]]);
|
|
});
|
|
$cleanup = app(ServiceCompanyReconciliationRepository::class)
|
|
->deactivateAutoMappingsWithoutReportIncome(
|
|
$this->company->id,
|
|
'2026-10',
|
|
'校正测试',
|
|
);
|
|
$this->assertSame(1, $cleanup['deactivated']);
|
|
$this->assertSame('inactive', $mirror->table('svc_service_relations')
|
|
->where('store_company_name', '上海测试店铺二公司有限公司')
|
|
->value('status'));
|
|
}
|
|
|
|
public function test_same_store_company_and_service_type_cannot_have_overlapping_service_companies(): void
|
|
{
|
|
$this->seedStoreMirror();
|
|
$this->loginAs();
|
|
$firstId = $this->createServiceCompany('第一测试客服有限公司', '91320400TESTCUSTOMER02');
|
|
$secondId = $this->createServiceCompany('第二测试客服有限公司', '91320400TESTCUSTOMER03');
|
|
$storeKey = ServiceCompanyReconciliationRepository::companyKey(
|
|
'上海测试店铺公司有限公司',
|
|
'91310000TESTSTORE01',
|
|
);
|
|
|
|
$payload = [
|
|
'store_company_key' => $storeKey,
|
|
'base_metric' => 'settlement_income',
|
|
'fee_rate_percent' => 1.5,
|
|
'effective_from' => '2026-01-01',
|
|
'effective_to' => '2026-12-31',
|
|
'contract_no' => 'HT-TEST-002',
|
|
'contract_status' => 'stamped',
|
|
'status' => 'active',
|
|
];
|
|
$this->post(route('business.service-company-reconciliation.relations.store', [
|
|
'serviceCompany' => $firstId,
|
|
]), $payload)->assertSessionHasNoErrors();
|
|
|
|
$this->withExceptionHandling()
|
|
->post(route('business.service-company-reconciliation.relations.store', [
|
|
'serviceCompany' => $secondId,
|
|
]), $payload)
|
|
->assertSessionHasErrors('effective_from');
|
|
|
|
$this->assertSame(
|
|
1,
|
|
DB::connection('business_mirror')->table('svc_service_relations')->count(),
|
|
);
|
|
}
|
|
|
|
public function test_monthly_preview_uses_income_amount_and_prevents_confirmed_overwrite(): void
|
|
{
|
|
$period = CarbonImmutable::now()->subMonthNoOverflow()->format('Y-m');
|
|
$this->seedStoreMirror($period);
|
|
$this->loginAs();
|
|
$serviceCompanyId = $this->createServiceCompany(
|
|
'常州测试客服服务有限公司',
|
|
'91320400TESTCUSTOMER04',
|
|
);
|
|
$storeKey = ServiceCompanyReconciliationRepository::companyKey(
|
|
'上海测试店铺公司有限公司',
|
|
'91310000TESTSTORE01',
|
|
);
|
|
$this->post(route('business.service-company-reconciliation.relations.store', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
]), [
|
|
'store_company_key' => $storeKey,
|
|
'base_metric' => 'settlement_income',
|
|
'fee_rate_percent' => 1.5,
|
|
'effective_from' => '2025-01-01',
|
|
'effective_to' => '',
|
|
'contract_no' => 'HT-TEST-003',
|
|
'contract_status' => 'stamped',
|
|
'status' => 'active',
|
|
])->assertSessionHasNoErrors();
|
|
|
|
$this->post(route('business.service-company-reconciliation.settlements.generate', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
]), ['period' => $period])->assertRedirect();
|
|
|
|
$settlement = DB::connection('business_mirror')
|
|
->table('svc_service_settlements')
|
|
->where('period', $period)
|
|
->first();
|
|
$this->assertNotNull($settlement);
|
|
$this->assertSame(125000.0, (float) $settlement->base_amount);
|
|
$this->assertSame(1875.0, (float) $settlement->service_fee);
|
|
$this->assertSame(
|
|
2,
|
|
DB::connection('business_mirror')->table('svc_service_settlement_details')->count(),
|
|
);
|
|
|
|
$this->patch(route('business.service-company-reconciliation.settlements.update', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
'settlement' => $settlement->id,
|
|
]), [
|
|
'action' => 'confirm',
|
|
'invoice_status' => 'invoiced',
|
|
'invoice_amount' => 1875,
|
|
'payment_status' => 'paid',
|
|
'paid_amount' => 1875,
|
|
])->assertSessionHasNoErrors();
|
|
|
|
$this->assertSame(
|
|
'confirmed',
|
|
DB::connection('business_mirror')
|
|
->table('svc_service_settlements')
|
|
->where('id', $settlement->id)
|
|
->value('status'),
|
|
);
|
|
|
|
$this->post(route('business.service-company-reconciliation.settlements.generate', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
]), ['period' => $period])
|
|
->assertRedirect()
|
|
->assertSessionHas('error');
|
|
|
|
$this->assertSame(
|
|
1875.0,
|
|
(float) DB::connection('business_mirror')
|
|
->table('svc_service_settlements')
|
|
->where('id', $settlement->id)
|
|
->value('service_fee'),
|
|
);
|
|
}
|
|
|
|
public function test_people_can_be_assigned_from_the_labor_snapshot(): void
|
|
{
|
|
$this->seedStoreMirror();
|
|
$this->loginAs();
|
|
$serviceCompanyId = $this->createServiceCompany(
|
|
'常州测试客服服务有限公司',
|
|
'91320400TESTCUSTOMER05',
|
|
);
|
|
DB::table('business_labor_expense_snapshots')->insert([
|
|
'company_id' => $this->company->id,
|
|
'source_name' => '测试工资.xlsx',
|
|
'rows' => json_encode([[
|
|
'B' => '小测',
|
|
'C' => '测试员工',
|
|
'D' => '客服一组',
|
|
'E' => '客服',
|
|
'F' => '8000',
|
|
]], JSON_UNESCAPED_UNICODE),
|
|
'row_count' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
$personKey = ServiceCompanyReconciliationRepository::personKey('测试员工', '小测', '客服一组');
|
|
|
|
$this->post(route('business.service-company-reconciliation.people.store', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
]), [
|
|
'person_key' => $personKey,
|
|
'effective_from' => now()->toDateString(),
|
|
])->assertSessionHasNoErrors();
|
|
|
|
$this->get(route('business.service-company-reconciliation.show', [
|
|
'serviceCompany' => $serviceCompanyId,
|
|
'tab' => 'people',
|
|
]))
|
|
->assertOk()
|
|
->assertSeeText('测试员工')
|
|
->assertSeeText('客服一组')
|
|
->assertSeeText('¥8,000.00');
|
|
}
|
|
|
|
public function test_finance_ledger_viewer_can_read_but_cannot_change_configuration(): void
|
|
{
|
|
$viewer = User::create([
|
|
'name' => 'fixture-service-company-viewer',
|
|
'email' => 'fixture-service-company-viewer@example.test',
|
|
'password' => 'fixture-unused-password',
|
|
'enabled' => true,
|
|
'locale' => 'zh-CN',
|
|
'landing_page' => 'dashboard',
|
|
]);
|
|
$viewer->roles()->attach(Role::where('name', 'user')->firstOrFail()->id);
|
|
$viewer->permissions()->attach(Permission::where('name', 'read-admin-panel')->firstOrFail()->id);
|
|
$viewer->companies()->attach($this->company->id);
|
|
$subject = 'user_' . $viewer->id;
|
|
setting()->set('system.sidebar_permissions', [
|
|
'configured' => [$subject],
|
|
'subjects' => [$subject => ['financial-operations/ledger']],
|
|
]);
|
|
|
|
$this->loginAs($viewer)
|
|
->get(route('business.service-company-reconciliation.index'))
|
|
->assertOk()
|
|
->assertSeeText('服务公司总览')
|
|
->assertDontSee('data-company-create>', false);
|
|
|
|
$this->withExceptionHandling()
|
|
->post(route('business.service-company-reconciliation.companies.store'), [
|
|
'company_name' => '不应创建的客服公司',
|
|
'service_type_code' => 'customer_service',
|
|
'company_status' => '正常',
|
|
'status' => 'active',
|
|
])
|
|
->assertForbidden();
|
|
}
|
|
|
|
private function createServiceCompany(string $name, string $code): int
|
|
{
|
|
$this->post(route('business.service-company-reconciliation.companies.store'), [
|
|
'company_name' => $name,
|
|
'unified_social_credit_code' => $code,
|
|
'service_type_code' => 'customer_service',
|
|
'taxpayer_type' => '小规模纳税人',
|
|
'company_status' => '正常',
|
|
'planning_target_amount' => 3000000,
|
|
'status' => 'active',
|
|
])->assertSessionHasNoErrors();
|
|
|
|
return (int) DB::connection('business_mirror')
|
|
->table('svc_service_companies')
|
|
->where('unified_social_credit_code', $code)
|
|
->value('id');
|
|
}
|
|
|
|
private function seedStoreMirror(?string $period = null): void
|
|
{
|
|
$period = $period ?: CarbonImmutable::now()->format('Y-m');
|
|
$periodStart = CarbonImmutable::createFromFormat('!Y-m', $period)->startOfMonth();
|
|
$mirror = DB::connection('business_mirror');
|
|
$paidGeneration = '11111111-1111-1111-1111-111111111111';
|
|
$lifecycleGeneration = '22222222-2222-2222-2222-222222222222';
|
|
|
|
$mirror->table('store_report_import_states')->insert([
|
|
[
|
|
'dataset_key' => 'store-report.paid-sales',
|
|
'status' => 'succeeded',
|
|
'active_generation' => $paidGeneration,
|
|
'row_count' => 2,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
],
|
|
[
|
|
'dataset_key' => 'store-report.store-lifecycles',
|
|
'status' => 'succeeded',
|
|
'active_generation' => $lifecycleGeneration,
|
|
'row_count' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
],
|
|
]);
|
|
|
|
foreach (['测试店铺A', '测试店铺B'] as $storeName) {
|
|
$mirror->table('store_report_lifecycle_rows')->insert([
|
|
'generation' => $lifecycleGeneration,
|
|
'business_key' => hash('sha256', '上海测试店铺公司有限公司|' . $storeName . '|2025-01-01'),
|
|
'erp_store_name' => $storeName,
|
|
'company_name' => '上海测试店铺公司有限公司',
|
|
'unified_social_credit_code' => '91310000TESTSTORE01',
|
|
'effective_at' => '2025-01-01 00:00:00',
|
|
'expires_at' => null,
|
|
'source_file' => 'test.xlsx',
|
|
'row_hash' => hash('sha256', 'lifecycle-row-' . $storeName),
|
|
'imported_at' => now(),
|
|
]);
|
|
}
|
|
|
|
foreach ([['测试店铺A', 100000], ['测试店铺B', 25000]] as [$storeName, $amount]) {
|
|
$mirror->table('store_report_paid_sales_rows')->insert([
|
|
'generation' => $paidGeneration,
|
|
'store_name' => $storeName,
|
|
'occurred_on' => $periodStart->addDays(8)->toDateString(),
|
|
'paid_amount' => $amount,
|
|
'settlement_income' => $amount,
|
|
'order_count' => 1,
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
}
|