diff --git a/app/Http/Controllers/Business/CourierAgreementSignatures.php b/app/Http/Controllers/Business/CourierAgreementSignatures.php index 41c30f6d..6dcb16d8 100644 --- a/app/Http/Controllers/Business/CourierAgreementSignatures.php +++ b/app/Http/Controllers/Business/CourierAgreementSignatures.php @@ -40,9 +40,13 @@ class CourierAgreementSignatures extends Controller $request->merge(['partner_type' => $request->input('partner_type', 'courier')]); $request->validate(['partner_type' => ['required', 'in:courier,supplier']]); if ($request->input('partner_type') === 'supplier') { - throw ValidationException::withMessages([ - 'partner_type' => '供应商资料尚未接入,暂不能发起供应商签署。请在资料接入后选择供应商。', - ]); + $request->validate(['supplier_id' => ['required', 'integer', 'min:1'], + 'document_type' => ['required', 'in:account-proof,account-change,entity-change,cooperation-contract,authorization']]); + $supplier = app(\Modules\EcommerceFinance\Repositories\SupplierManagementMirrorRepository::class) + ->signingSupplier((int) $request->input('supplier_id')); + if (! $supplier) throw ValidationException::withMessages(['supplier_id' => '供应商资料暂不可用,请重新选择供应商。']); + $request->merge(['courier_name' => trim((string) $supplier->scmname)]); + if (! $request->filled('esign_account')) $request->merge(['esign_account' => trim((string) $supplier->accountbankno)]); } abort_unless(EsignClient::configured(), 422, 'E 签宝沙箱尚未配置 App ID 或 AppSecret。'); $requestedType = (string) $request->input('document_type'); @@ -413,6 +417,7 @@ class CourierAgreementSignatures extends Controller return [ 'partner_type' => ['required', 'in:courier,supplier'], + 'supplier_id' => ['required_if:partner_type,supplier', 'nullable', 'integer', 'min:1'], 'idempotency_key' => ['required', 'string', 'max:80', 'regex:/^[A-Za-z0-9_-]+$/'], 'courier_name' => ['required', 'string', 'max:255'], 'document_type' => ['required', 'in:' . implode(',', array_keys(CourierAgreementSignature::DOCUMENTS))], diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 7a1c34de..e558a0ce 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -1633,6 +1633,7 @@ class Pages extends Controller $receiptStats = null; $agreementCourierNames = $this->agreementCourierNames($page['slug'] ?? null); $agreementCourierAccounts = $this->agreementCourierAccounts($page['slug'] ?? null, $agreementCourierNames); + $agreementSupplierOptions = $this->agreementSupplierOptions($page['slug'] ?? null); // 内部流转报表读取共享业务镜像中由工作簿导入的数据集,不走上游只读查询。 if ($page && $content === null && $this->isInternalFlowReportPage($section, $page)) { @@ -1710,6 +1711,7 @@ class Pages extends Controller 'agreementSubjects' => $this->agreementSubjects($page['slug']), 'agreementCourierNames' => $agreementCourierNames, 'agreementCourierAccounts' => $agreementCourierAccounts, + 'agreementSupplierOptions' => $agreementSupplierOptions, 'agreementQuoteVersions' => $this->agreementQuoteVersions($page['slug']), 'inventoryWarehouseMappings' => $inventoryWarehouseMappings, 'receiptStats' => $receiptStats, @@ -1770,6 +1772,7 @@ class Pages extends Controller 'agreementSubjects' => $this->agreementSubjects($page['slug'] ?? null), 'agreementCourierNames' => $agreementCourierNames, 'agreementCourierAccounts' => $agreementCourierAccounts, + 'agreementSupplierOptions' => $agreementSupplierOptions, 'agreementQuoteVersions' => $this->agreementQuoteVersions($page['slug'] ?? null), 'inventoryWarehouseMappings' => $inventoryWarehouseMappings, 'receiptStats' => $receiptStats, @@ -2173,6 +2176,17 @@ class Pages extends Controller } } + protected function agreementSupplierOptions(?string $page): array + { + if ($page !== 'agreement-signing') return []; + try { + return app(\Modules\EcommerceFinance\Repositories\SupplierManagementMirrorRepository::class)->signingOptions(); + } catch (Throwable $exception) { + report($exception); + return []; + } + } + /** @return array */ protected function agreementCourierAccounts(?string $page, ?Collection $courierNames = null): array { diff --git a/config/upstream_mirrors.php b/config/upstream_mirrors.php index 474c34a7..5cbe08e6 100644 --- a/config/upstream_mirrors.php +++ b/config/upstream_mirrors.php @@ -210,7 +210,7 @@ return [ 'target' => 'js_supplier_management', 'keys' => ['Id'], 'full_reconcile_hours' => 1, 'columns' => ['Id', 'guid', 'sourcefrom', 'coopersgn', 'scmcode', 'scmname', 'scmtype', - 'tradebody', 'company', 'enterpriseType', 'creditcode', 'handlePerson', 'legalPerson', + 'tradebody', 'company', 'enterpriseType', 'creditcode', 'handlePerson', 'accountbankno', 'legalPerson', 'buyer', 'tradebtime', 'tradeetime', 'cooperbtime', 'cooperetime', 'memo', 'updator', 'local_created', 'local_updated'], 'indexes' => [['scmcode', 'Id'], ['coopersgn', 'Id']], diff --git a/docs/上游业务镜像清单.md b/docs/上游业务镜像清单.md index 6dcf1f73..10197066 100644 --- a/docs/上游业务镜像清单.md +++ b/docs/上游业务镜像清单.md @@ -66,7 +66,7 @@ | 店铺收款账单旧来源(已停用) | `dbo.alipay_Bill_Detail` | | 营销账户 | `dbo.tmall_spread_bills`、`dbo.pdd_spread_bills`、`dbo.alibaba_bill`、`dbo.jd_spreadBills`、`dbo.jd_costBills` | | 供应商应付 | `dbo.finance_purchaseReturn`、`dbo.finance_invoiceDeduction`、`dbo.fys_supplierPaymentSummary`、`dbo.finance_advanceLoans`、`dbo.pm_unOrderDetails`、`dbo.finance_sampleDeduction`、`dbo.fys_express_courier_settlement` | -| 供应商管理(简尚版) | `jiansun.dbo.js_supplier_View`(`Id` 稳定键,镜像 `js_supplier_management`)、`dbo.js_supplier_contract_View`(`id` 稳定键,镜像 `js_supplier_contracts`)、`dbo.js_supplier_linkers`(镜像 `js_supplier_contacts`)与 `dbo.js_supplier_payinfo`(镜像 `js_supplier_payment_accounts`)。联系人、打款资料按 `Id` / `local_updated` 增量并每 24 小时全量核对;主视图及合同视图无可靠更新时间,按稳定键追加并每小时全量校准。复用现有轮转调度、游标与代次,未完成校准继续全量模式。页面按供应商名称、采购人员及合作状态筛选;合同同时匹配合同主体和签订公司,联系人及账户按供应商名称关联。未完成首次同步时明确提示,不在页面请求中直连上游;操作日志关联未确认时不展示其他模块日志。 | +| 供应商管理(简尚版) | `jiansun.dbo.js_supplier_View`(`Id` 稳定键,镜像 `js_supplier_management`)、`dbo.js_supplier_contract_View`(`id` 稳定键,镜像 `js_supplier_contracts`)、`dbo.js_supplier_linkers`(镜像 `js_supplier_contacts`)与 `dbo.js_supplier_payinfo`(镜像 `js_supplier_payment_accounts`)。联系人、打款资料按 `Id` / `local_updated` 增量并每 24 小时全量核对;主视图及合同视图无可靠更新时间,按稳定键追加并每小时全量校准。复用现有轮转调度、游标与代次,未完成校准继续全量模式。页面按供应商名称、采购人员及合作状态筛选;合同同时匹配合同主体和签订公司,联系人及账户按供应商名称关联。`accountbankno` 对应上游“电子合同经办人”账号,`handlePerson` 是经办人实名姓名,两者分别使用;发起签署按正式供应商 `Id` 选择并从该镜像带入账号,支持简称、合同主体全称及编码搜索,账号未维护时保留空值供手工核对,不用联系人电话替代。未完成首次同步时明确提示,不在页面请求中直连上游;操作日志关联未确认时不展示其他模块日志。 | | 快递运费对账明细 | `dbo.jst_order_express_details`、`dbo.Jst_SendData_View_extension`、`dbo.jst_salesOutboundInfo` | | 工作平台退货取件管理 | `dbo.ekf_ReturnPickupManagement`、`dbo.ekf_ReturnPickupManagement_Item`、`dbo.ekf_ReturnPickupManagement_TruthReExpressNumber`、发货仓名称 `dbo.wp_warehouse`、`dbo.wp_warehouseConfig` | | 聚水潭进销存 | `dbo.jst_inoutWater`、`dbo.jst_inoutWaterItem`、`dbo.jst_inventoryInfo`(全部仓库、`io_date >= 2026-08-01`;专用稳定游标与完整代次镜像。三方仓页面仍固定 `wms_co_id = 15045364`,商品价格管理的进销存报表读取全仓库) | diff --git a/modules/EcommerceFinance/Repositories/SupplierManagementMirrorRepository.php b/modules/EcommerceFinance/Repositories/SupplierManagementMirrorRepository.php index 1d20b748..fd419159 100644 --- a/modules/EcommerceFinance/Repositories/SupplierManagementMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/SupplierManagementMirrorRepository.php @@ -21,7 +21,7 @@ class SupplierManagementMirrorRepository 'sourcefrom' => '数据来源', 'coopersgn' => '是否合作', 'scmcode' => '供应商代码', 'scmname' => '供应商名称', 'scmtype' => '供应商类型', 'tradebody' => '合同主体', 'company' => '签订公司', 'enterpriseType' => '企业类型', 'creditcode' => '社会信用代码', - 'handlePerson' => '电子合同经办人', 'buyer' => '采购人员', + 'accountbankno' => '电子合同经办人', 'buyer' => '采购人员', ]; public function __construct( @@ -40,6 +40,33 @@ class SupplierManagementMirrorRepository return $this->mirror->state($this->datasets->get(self::DATASET)); } + /** 正式供应商签署资料;accountbankno 是上游电子合同账号,handlePerson 是实名姓名。 */ + public function signingOptions(): array + { + if (! $this->signingReady()) return []; + return $this->mirror->connection()->table($this->datasets->get(self::DATASET)->targetTable()) + ->orderBy('scmname')->orderBy('Id') + ->get(['Id', 'scmname', 'scmcode', 'tradebody', 'accountbankno', 'handlePerson']) + ->map(static fn (object $row): array => [ + 'id' => (int) $row->Id, 'value' => trim((string) $row->scmname), + 'code' => trim((string) $row->scmcode), 'company' => trim((string) $row->tradebody), + 'account' => trim((string) $row->accountbankno), 'operator' => trim((string) $row->handlePerson), + ])->all(); + } + + public function signingSupplier(int $id): ?object + { + if (! $this->signingReady()) return null; + return $this->mirror->connection()->table($this->datasets->get(self::DATASET)->targetTable()) + ->where('Id', $id)->first(['Id', 'scmname', 'tradebody', 'accountbankno', 'handlePerson']); + } + + private function signingReady(): bool + { + return $this->ready() && $this->mirror->connection()->getSchemaBuilder() + ->hasColumn($this->datasets->get(self::DATASET)->targetTable(), 'accountbankno'); + } + public function paginate(array $filters, int $page, int $limit): array { $query = $this->query($filters); diff --git a/resources/views/business/pages/courier-payables/agreement-signing.blade.php b/resources/views/business/pages/courier-payables/agreement-signing.blade.php index 6ab58d03..4809ad90 100644 --- a/resources/views/business/pages/courier-payables/agreement-signing.blade.php +++ b/resources/views/business/pages/courier-payables/agreement-signing.blade.php @@ -3,6 +3,7 @@ @php($agreementSubjects = $agreementSubjects ?? collect()) @php($agreementCourierNames = collect($agreementCourierNames ?? [])) @php($agreementCourierAccounts = $agreementCourierAccounts ?? []) +@php($agreementSupplierOptions = $agreementSupplierOptions ?? []) @php($agreementQuoteVersions = $agreementQuoteVersions ?? collect()) @php($agreementCooperationSecondaryEsignAccount = '18052733960') @php($agreementDefaultSubject = $agreementSubjects->firstWhere('is_default', true) ?: $agreementSubjects->first()) @@ -1587,10 +1588,8 @@ - + +
@@ -2249,6 +2248,8 @@ var courierLabel = root.querySelector('label[for="agreement-courier"]'); var partnerTypeInputs = Array.prototype.slice.call(root.querySelectorAll('[data-partner-type]')); var supplierControl = root.querySelector('[data-supplier-control]'); + var supplierIdInput = root.querySelector('[data-signing-supplier-id]'); + var supplierOptions = @json($agreementSupplierOptions); var documentSelect = form.querySelector('[data-document-type]'); var documentOptions = Array.prototype.slice.call(documentSelect.options); var currentPartnerType = 'courier'; @@ -2259,6 +2260,8 @@ }).map(function (option) { return { value: option.value, account: option.dataset.esignAccount || '' }; }) : []; + var originalCourierOptions = courierOptions; + var selectedSupplierId = ''; var visibleCourierIndexes = []; var activeCourierIndex = -1; var esignAccountInput = root.querySelector('input[name="esign_account"]'); @@ -2759,16 +2762,29 @@ secondaryEsignAccountInput.value = ''; secondaryOperatorInput.value = ''; root.querySelector('[data-request-error]').textContent = ''; + supplierIdInput.value = ''; + ['former_company_name', 'cooperation_party_b_name', 'principal_name'].forEach(function (name) { + var input = form.querySelector('[name="' + name + '"]'); + if (input) input.value = ''; + }); } currentPartnerType = type; - courierControl.hidden = supplier; - courierSelect.disabled = supplier; - courierSearch.disabled = supplier; + courierOptions = supplier ? supplierOptions : originalCourierOptions; + courierSelect.replaceChildren(new Option('请选择', '')); + courierOptions.forEach(function (item) { + var option = new Option(item.value, item.value); + option.dataset.esignAccount = item.account; + courierSelect.appendChild(option); + }); + supplierIdInput.disabled = !supplier; + courierSearch.placeholder = supplier ? '输入供应商简称、全称或编码搜索' : '输入快递名称搜索'; + courierOptionsList.setAttribute('aria-label', supplier ? '供应商资料' : '快递资料'); supplierControl.hidden = !supplier; - courierLabel.htmlFor = supplier ? 'agreement-supplier-search' : courierSearch.id; + supplierControl.textContent = supplierOptions.length ? '按供应商简称、企业全称或编码搜索,选择后自动带入易签宝账号。' : '供应商资料暂不可用,请稍后重试。'; + courierLabel.htmlFor = courierSearch.id; root.querySelector('[data-partner-name-label]').textContent = supplier ? '供应商名称' : '快递公司'; root.querySelector('[data-partner-account-help]').textContent = supplier - ? '供应商资料接入后可带出签署账号和经办人;发送前需核对对方 E 签宝实名信息。' + ? '选择供应商后自动带出工作平台的电子合同经办人账号;发送前请核对企业全称、账号及实名认证姓名,资料未维护时可手工填写。' : '选择快递公司后自动带出工作平台的“电子签章经办人”(dzqzjbr);如资料未维护,可手工填写对方在 E 签宝实名绑定的手机号或邮箱。'; // Rebuild the small template list so unavailable options are absent on every browser. var previousDocument = documentSelect.value; @@ -2782,9 +2798,10 @@ } else { documentSelect.value = 'account-proof'; } - sendButton.disabled = supplier || !esignAvailable; - sendButton.title = supplier ? '供应商资料尚未接入,暂不能发起签署' : (esignAvailable ? '推送至E签宝' : 'E签宝沙箱应用尚未配置'); - sendButton.querySelector('span:last-child').textContent = supplier ? '供应商资料待接入' : '推送至E签宝'; + sendButton.disabled = (supplier && !supplierOptions.length) || !esignAvailable; + sendButton.title = supplier && !supplierOptions.length ? '供应商资料暂不可用' : (esignAvailable ? '推送至E签宝' : 'E签宝尚未配置'); + sendButton.querySelector('span:last-child').textContent = '推送至E签宝'; + syncCourierSelection(); } function fieldValue(selector, fallback) { @@ -2851,11 +2868,30 @@ var option = courierSelect.options[courierSelect.selectedIndex]; var account = option && option.dataset.esignAccount ? option.dataset.esignAccount.trim() : ''; + if (currentPartnerType === 'supplier') { + var supplier = courierOptions.find(function (item) { return String(item.id) === supplierIdInput.value; }); + if (selectedSupplierId !== supplierIdInput.value) { + selectedSupplierId = supplierIdInput.value; + esignAccountInput.value = ''; + delete esignAccountInput.dataset.autoFilled; + accountOrganizationInput.value = supplier ? supplier.company : ''; + operatorNameInput.value = supplier ? supplier.operator : ''; + ['former_company_name', 'cooperation_party_b_name', 'principal_name'].forEach(function (name) { + var input = form.querySelector('[name="' + name + '"]'); + if (input) input.value = supplier ? supplier.company : ''; + }); + } + supplierControl.textContent = supplier + ? supplier.value + ' · ' + supplier.code + (supplier.company ? ' · ' + supplier.company : '') + (supplier.account ? '' : ';未维护易签宝账号,请手工填写并核对。') + : (supplierOptions.length ? '请从搜索结果选择供应商。' : '供应商资料暂不可用,请稍后重试。'); + } else { + selectedSupplierId = ''; + } if (account !== '') { esignAccountInput.value = account; esignAccountInput.dataset.autoFilled = 'true'; - announce('已带出该快递的电子签章经办人账号'); + announce(currentPartnerType === 'supplier' ? '已带出该供应商的易签宝账号' : '已带出该快递的电子签章经办人账号'); } else if (esignAccountInput.dataset.autoFilled === 'true') { esignAccountInput.value = ''; delete esignAccountInput.dataset.autoFilled; @@ -2882,7 +2918,8 @@ var matches = []; courierOptions.forEach(function (option, index) { - if (normalizedQuery === '' || option.value.toLocaleLowerCase().indexOf(normalizedQuery) !== -1) { + var searchText = [option.value, option.company || '', option.code || ''].join(' ').toLocaleLowerCase(); + if (normalizedQuery === '' || searchText.indexOf(normalizedQuery) !== -1) { matches.push(index); } }); @@ -2894,7 +2931,9 @@ var empty = document.createElement('div'); empty.className = 'agreement-courier-options-status'; empty.setAttribute('role', 'status'); - empty.textContent = normalizedQuery === '' ? '暂无可选快递资料' : '没有匹配的快递名称'; + empty.textContent = currentPartnerType === 'supplier' + ? (normalizedQuery === '' ? '暂无可选供应商资料' : '没有匹配的供应商') + : (normalizedQuery === '' ? '暂无可选快递资料' : '没有匹配的快递名称'); courierOptionsList.appendChild(empty); } else { visibleCourierIndexes.forEach(function (optionIndex) { @@ -2905,7 +2944,7 @@ item.setAttribute('role', 'option'); item.setAttribute('aria-selected', courierSelect.value === option.value ? 'true' : 'false'); item.dataset.courierOption = String(optionIndex); - item.textContent = option.value; + item.textContent = currentPartnerType === 'supplier' ? option.value + ' · ' + option.code + (option.company ? ' · ' + option.company : '') : option.value; courierOptionsList.appendChild(item); }); @@ -2948,6 +2987,7 @@ var option = courierOptions.find(function (item) { return item.value.toLocaleLowerCase() === enteredName.toLocaleLowerCase(); }); + supplierIdInput.value = currentPartnerType === 'supplier' && option ? option.id : ''; if (option) { courierSearch.value = option.value; @@ -2955,7 +2995,7 @@ courierSearch.setCustomValidity(''); } else { courierSelect.value = ''; - courierSearch.setCustomValidity(enteredName === '' ? '请选择快递公司' : '请从快递资料中选择快递公司'); + courierSearch.setCustomValidity(currentPartnerType === 'supplier' ? '请从供应商资料中选择供应商' : (enteredName === '' ? '请选择快递公司' : '请从快递资料中选择快递公司')); } } @@ -3237,7 +3277,7 @@ function refreshPreview() { var documentOption = selectedDocument(); var templateType = documentOption.value; - var courier = currentPartnerType === 'supplier' ? '供应商' : fieldValue('[name="courier_name"]', '快递公司'); + var courier = fieldValue('[name="courier_name"]', currentPartnerType === 'supplier' ? '供应商' : '快递公司'); var companyName = fieldValue('[name="company_full_name"]', '待填写公司全称'); var accountAgreement = templateType === 'account-proof' || templateType === 'account-change'; var entityChange = templateType === 'entity-change'; @@ -3659,7 +3699,7 @@ input.addEventListener('change', function () { syncPartnerType(); refreshPreview(); - announce(currentPartnerType === 'supplier' ? '已切换到供应商,资料尚未接入' : '已切换到快递公司'); + announce(currentPartnerType === 'supplier' ? '已切换到供应商' : '已切换到快递公司'); }); }); @@ -3669,11 +3709,6 @@ }); form.addEventListener('submit', function (event) { - if (currentPartnerType === 'supplier') { - event.preventDefault(); - root.querySelector('[data-request-error]').textContent = '供应商资料尚未接入,暂不能发起供应商签署。'; - return; - } refreshPreview(); if (!form.checkValidity()) { event.preventDefault(); diff --git a/tests/Feature/Business/AgreementSigningPageTest.php b/tests/Feature/Business/AgreementSigningPageTest.php index 1e391090..2c33f2a1 100644 --- a/tests/Feature/Business/AgreementSigningPageTest.php +++ b/tests/Feature/Business/AgreementSigningPageTest.php @@ -11,6 +11,7 @@ use App\Services\Esign\EsignClient; use Illuminate\Support\Facades\DB; use Mockery; use Modules\EcommerceFinance\Repositories\CourierInformationMirrorRepository; +use Modules\EcommerceFinance\Repositories\SupplierManagementMirrorRepository; use Symfony\Component\HttpKernel\Exception\HttpException; use Tests\Feature\FeatureTestCase; @@ -65,7 +66,8 @@ class AgreementSigningPageTest extends FeatureTestCase ->assertSeeText('合作方类型') ->assertSee('name="partner_type" value="courier" checked', false) ->assertSee('name="partner_type" value="supplier"', false) - ->assertSeeText('供应商资料尚未接入,暂不能选择或发起签署。') + ->assertSee('data-signing-supplier-id', false) + ->assertSeeText('按供应商简称、企业全称或编码搜索,选择后自动带入易签宝账号。') ->assertSeeText('快递公司') ->assertSee('data-courier-search', false) ->assertSee('placeholder="输入快递名称搜索"', false) @@ -107,6 +109,21 @@ class AgreementSigningPageTest extends FeatureTestCase ->assertSeeText('收款帐户资料证明'); } + public function test_signing_page_exposes_supplier_account_and_search_identity_separately(): void + { + $this->grantWorkspaceAccess(); + $repository = Mockery::mock(SupplierManagementMirrorRepository::class); + $repository->shouldReceive('signingOptions')->once()->andReturn([[ + 'id' => 7, 'value' => '测试供应商', 'code' => 'T007', 'company' => '测试供应商有限公司', + 'account' => '13800138000', 'operator' => '张经理', + ]]); + $this->app->instance(SupplierManagementMirrorRepository::class, $repository); + $response = $this->loginAs()->get(route('business.pages.show', ['section' => 'courier-payables', + 'page' => 'agreement-signing', 'agreement_view' => 'create']))->assertOk(); + $this->assertSame('13800138000', $response->viewData('agreementSupplierOptions')[0]['account']); + $this->assertSame('张经理', $response->viewData('agreementSupplierOptions')[0]['operator']); + } + public function testAgreementSigningPageUsesCourierNamesFromCourierInformation(): void { $this->grantWorkspaceAccess(); diff --git a/tests/Feature/Business/CourierAgreementSignatureWorkflowTest.php b/tests/Feature/Business/CourierAgreementSignatureWorkflowTest.php index 9ec73219..8e64b2b5 100644 --- a/tests/Feature/Business/CourierAgreementSignatureWorkflowTest.php +++ b/tests/Feature/Business/CourierAgreementSignatureWorkflowTest.php @@ -15,6 +15,8 @@ use Illuminate\Http\Client\Request as ClientRequest; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Storage; +use Mockery; +use Modules\EcommerceFinance\Repositories\SupplierManagementMirrorRepository; use Tests\Feature\FeatureTestCase; class CourierAgreementSignatureWorkflowTest extends FeatureTestCase @@ -73,7 +75,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase $this->assertSame(1, CourierAgreementSignature::query()->count()); } - public function test_supplier_requests_are_rejected_before_creating_or_sending_any_document(): void + public function test_supplier_requests_require_a_selected_supplier_before_sending(): void { Http::preventStrayRequests(); $this->withExceptionHandling(); @@ -81,12 +83,71 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase $payload['partner_type'] = 'supplier'; $this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload) - ->assertSessionHasErrors(['partner_type' => '供应商资料尚未接入,暂不能发起供应商签署。请在资料接入后选择供应商。']); + ->assertSessionHasErrors('supplier_id'); $this->assertSame(0, CourierAgreementSignature::query()->count()); Http::assertNothingSent(); } + public function test_supplier_request_uses_selected_supplier_and_its_signing_account(): void + { + $this->fakeEsign(); + $repository = Mockery::mock(SupplierManagementMirrorRepository::class); + $repository->shouldReceive('signingSupplier')->with(7)->once()->andReturn((object) [ + 'Id' => 7, 'scmname' => '测试供应商', 'accountbankno' => '13900139000', + ]); + $this->app->instance(SupplierManagementMirrorRepository::class, $repository); + $payload = $this->accountProofPayload('supplier-signing'); + $payload['partner_type'] = 'supplier'; + $payload['supplier_id'] = 7; + $payload['courier_name'] = '伪造名称'; + unset($payload['esign_account']); + $this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertRedirect(); + $signature = CourierAgreementSignature::query()->firstOrFail(); + $this->assertSame('测试供应商', $signature->courier_name); + $this->assertSame('13900139000', $signature->operator_account); + $this->assertSame('supplier', $signature->payload['partner_type']); + $this->assertSame(7, $signature->payload['supplier_id']); + $this->assertSame(CourierAgreementSignature::STATUS_SIGNING, $signature->status); + } + + public function test_invalid_supplier_and_courier_only_templates_never_send(): void + { + Http::preventStrayRequests(); + $this->withExceptionHandling(); + $repository = Mockery::mock(SupplierManagementMirrorRepository::class); + $repository->shouldReceive('signingSupplier')->with(999)->once()->andReturnNull(); + $this->app->instance(SupplierManagementMirrorRepository::class, $repository); + $payload = $this->accountProofPayload('supplier-invalid'); + $payload['partner_type'] = 'supplier'; + $payload['supplier_id'] = 999; + $this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertSessionHasErrors('supplier_id'); + $payload['document_type'] = 'quote-confirmation'; + $this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertSessionHasErrors('document_type'); + $this->assertSame(0, CourierAgreementSignature::query()->count()); + Http::assertNothingSent(); + } + + public function test_supplier_cooperation_contract_keeps_both_signing_parties(): void + { + $this->fakeEsign(); + $repository = Mockery::mock(SupplierManagementMirrorRepository::class); + $repository->shouldReceive('signingSupplier')->with(7)->once()->andReturn((object) [ + 'Id' => 7, 'scmname' => '测试供应商', 'accountbankno' => '13900139000', + ]); + $this->app->instance(SupplierManagementMirrorRepository::class, $repository); + $payload = $this->cooperationContractPayload('supplier-cooperation'); + $payload['partner_type'] = 'supplier'; + $payload['supplier_id'] = 7; + unset($payload['esign_account']); + $this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertRedirect(); + $signature = CourierAgreementSignature::query()->firstOrFail(); + $this->assertSame('测试供应商', $signature->courier_name); + $this->assertSame('13900139000', $signature->operator_account); + $this->assertSame('18052733960', $signature->payload['secondary_esign_account']); + $this->assertSame(CourierAgreementSignature::STATUS_SIGNING, $signature->status); + } + public function test_unknown_partner_type_is_rejected_before_sending(): void { Http::preventStrayRequests(); diff --git a/tests/Unit/EcommerceFinance/SupplierManagementMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/SupplierManagementMirrorRepositoryTest.php index 709e82d6..7a75e177 100644 --- a/tests/Unit/EcommerceFinance/SupplierManagementMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/SupplierManagementMirrorRepositoryTest.php @@ -96,10 +96,29 @@ class SupplierManagementMirrorRepositoryTest extends TestCase { $this->table('js_supplier_management')->insert(['Id' => 1]); $this->assertFalse($this->repository->ready()); + $this->assertSame([], $this->repository->signingOptions()); + $this->assertNull($this->repository->signingSupplier(1)); $this->expectException(\RuntimeException::class); $this->repository->paginate([], 1, 20); } + public function test_signing_accounts_use_the_account_field_and_keep_real_names_separate(): void + { + $this->ready(SupplierManagementMirrorRepository::DATASET); + $this->table('js_supplier_management')->insert([ + ['Id' => 7, 'scmname' => '测试供应商', 'scmcode' => 'T007', 'tradebody' => '测试供应商有限公司', + 'accountbankno' => '13800138000', 'handlePerson' => '张经理'], + ['Id' => 8, 'scmname' => '缺账号供应商', 'scmcode' => 'T008', 'tradebody' => '', + 'accountbankno' => null, 'handlePerson' => '李经理'], + ]); + $options = collect($this->repository->signingOptions())->keyBy('id'); + $this->assertSame('13800138000', $options[7]['account']); + $this->assertSame('张经理', $options[7]['operator']); + $this->assertSame('', $options[8]['account']); + $this->assertSame('13800138000', $this->repository->signingSupplier(7)->accountbankno); + $this->assertNull($this->repository->signingSupplier(999)); + } + private function ready(string $key): void { $this->table('business_mirror_table_states')->insert(['dataset_key' => $key, 'initial_completed_at' => '2026-10-11 00:00:00']);