供应商对账:单价/金额留空自动补齐,未定价行厂家也能直接上传

实测雪翔家居 9 月对账单:表头已改对(10 列与模板一致),但第 11 行是未定价行(数量 52、单价为空、金额 0),按「单价必填」会被拒绝。现在单价留空按「金额÷数量」补齐、金额留空按「数量×单价」补齐、两者都空按 0(数量仍必填),厂家直接导出即可上传。
This commit is contained in:
苏木 2026-10-17 09:16:15 +08:00
parent 2449c48c78
commit 85e2f05043
3 changed files with 44 additions and 3 deletions

View File

@ -28,8 +28,8 @@ class SupplierStatementTemplate
private const MAX_ROWS = 200000;
/** 允许留空的列。 */
private const OPTIONAL_COLUMNS = ['expressName', 'expressNo', 'purchaseNo'];
/** 允许留空的列:快递信息可缺;单价/金额留空时按「金额÷数量」或「数量×单价」补齐,都空按 0。 */
private const OPTIONAL_COLUMNS = ['expressName', 'expressNo', 'purchaseNo', 'unitPrice', 'amount'];
/** 必须是数字的列。 */
private const NUMERIC_COLUMNS = ['qty', 'unitPrice', 'amount'];
@ -96,6 +96,8 @@ class SupplierStatementTemplate
}
}
$values = $this->completeAmounts($values);
if ($expectedSupplier !== null && $values['supplier'] !== $expectedSupplier) {
throw ValidationException::withMessages([
'import' => "第 {$rowNumber} 行:供应商必须填写「{$expectedSupplier}」,不能提交其他厂家的数据。",
@ -194,4 +196,24 @@ class SupplierStatementTemplate
return mb_strtolower($label);
}
/** 单价/金额留空时互相补算;两者都空按 0(未定价行厂家直接导出时会出现)。 */
private function completeAmounts(array $values): array
{
$qty = (float) ($values['qty'] ?? 0);
$unitPrice = $values['unitPrice'] === null ? null : (float) $values['unitPrice'];
$amount = $values['amount'] === null ? null : (float) $values['amount'];
if ($unitPrice === null && $amount !== null && $qty != 0.0) {
$values['unitPrice'] = (string) round($amount / $qty, 4);
} elseif ($unitPrice === null) {
$values['unitPrice'] = '0';
}
if ($amount === null) {
$values['amount'] = (string) round($qty * (float) $values['unitPrice'], 2);
}
return $values;
}
}

View File

@ -41,7 +41,8 @@
<a href="{{ $statementTemplateUrl }}" class="sr-button">下载对账单模板</a>
</form>
<p class="sr-hint">
厂家只能按模板上传(表头列名与顺序必须与模板完全一致):快递名称、快递单号、单据编号(采购单号)可以留空,其余列必填;
厂家只能按模板上传(表头列名与顺序必须与模板完全一致):快递名称、快递单号、单据编号(采购单号)可以留空;
单价或金额留空时按数量自动补齐(都留空按 0,未定价行可直接导出上传);其余列必填;
不符合模板或数值格式的整份失败并提示具体行号。厂家上传成功后,下面的数据与「财务端数据」的比对结果立即更新。
</p>
@if ($selectedSupplier !== '')

View File

@ -240,6 +240,24 @@ class SupplierReconciliationTest extends FeatureTestCase
}
}
public function test_template_completes_missing_unit_price_or_amount(): void
{
$file = UploadedFile::fake()->createWithContent(
'statement.csv',
"\xEF\xBB\xBF" . self::HEADER . "\n"
. ',,编号-1,2026-09-30,晨丰,ZSJ-1,规格甲,24,,2916' . "\n" // 单价空 → 2916/24
. ',,编号-2,2026-09-30,晨丰,ZSJ-2,规格甲,3,10,' . "\n", // 金额空 → 3×10
);
$records = app(SupplierStatementTemplate::class)->parse($file, '晨丰');
$this->assertCount(2, $records);
$this->assertSame('121.5', $records[0]['unitPrice']);
$this->assertSame('2916', $records[0]['amount']);
$this->assertSame('10', $records[1]['unitPrice']);
$this->assertSame('30', $records[1]['amount']);
}
private function statementRow(string $barcode, string $unitPrice, string $qty, string $amount): array
{
return [