> $mine 我方结算数据(货款对账:skuCode/unitPrice/qty/amount) * @param list> $theirs 厂家对账单(barcode/unitPrice/qty/amount) * @return array{rows:list>,summary:array} */ public function compare(array $mine, array $theirs): array { $mineGrouped = $this->group($mine, 'skuCode'); $theirsGrouped = $this->group($theirs, 'barcode'); $rows = []; $summary = ['match' => 0, 'supplierExtra' => 0, 'mismatch' => 0, 'supplierMissing' => 0, 'mineAmount' => 0.0, 'settleAmount' => 0.0]; foreach (array_unique(array_merge(array_keys($mineGrouped), array_keys($theirsGrouped))) as $key) { $mineRow = $mineGrouped[$key] ?? null; $theirsRow = $theirsGrouped[$key] ?? null; $barcode = (string) ($mineRow['barcode'] ?? $theirsRow['barcode']); $supplierBarcode = (string) ($theirsRow['barcode'] ?? ''); $unitPrice = (float) ($mineRow['unitPrice'] ?? $theirsRow['unitPrice']); $mineQty = (float) ($mineRow['qty'] ?? 0); $mineAmount = (float) ($mineRow['amount'] ?? 0); $theirsQty = (float) ($theirsRow['qty'] ?? 0); $theirsAmount = (float) ($theirsRow['amount'] ?? 0); if ($mineRow === null) { $type = self::TYPE_SUPPLIER_EXTRA; $settle = 0.0; $note = '我方没有这条数据:不结算,退回厂家修改账单'; $summary['supplierExtra']++; } elseif ($theirsRow === null) { $type = self::TYPE_SUPPLIER_MISSING; $settle = 0.0; $note = '厂家少报了这条数据:按厂家数据结算(不付款)'; $summary['supplierMissing']++; } elseif (abs($mineAmount - $theirsAmount) < 0.005 && abs($mineQty - $theirsQty) < 0.005) { $type = self::TYPE_MATCH; $settle = $mineAmount; $note = '正常结算'; $summary['match']++; } else { $type = self::TYPE_MISMATCH; $settle = min($mineAmount, $theirsAmount); $note = '金额/数量不一致:按金额低的一方结算'; $summary['mismatch']++; } $summary['mineAmount'] += $mineAmount; $summary['settleAmount'] += $settle; $rows[] = [ 'barcode' => $barcode, 'supplierBarcode' => $supplierBarcode === $barcode ? '' : $supplierBarcode, 'unitPrice' => $unitPrice, 'mineQty' => $mineQty, 'mineAmount' => $mineAmount, 'theirsQty' => $theirsQty, 'theirsAmount' => $theirsAmount, 'type' => $type, 'settleAmount' => $settle, 'note' => $note, ]; } usort($rows, static fn (array $a, array $b): int => [$a['type'] === self::TYPE_MATCH ? 1 : 0, $a['barcode']] <=> [$b['type'] === self::TYPE_MATCH ? 1 : 0, $b['barcode']]); $summary['mineAmount'] = round($summary['mineAmount'], 2); $summary['settleAmount'] = round($summary['settleAmount'], 2); $summary['differenceAmount'] = round($summary['mineAmount'] - $summary['settleAmount'], 2); return ['rows' => $rows, 'summary' => $summary]; } /** * 按「条码 + 单价」归组,数量与金额相加。 * * @param list> $rows * @return array */ private function group(array $rows, string $barcodeField): array { $grouped = []; foreach ($rows as $row) { $barcode = trim((string) ($row[$barcodeField] ?? '')); if ($barcode === '') { continue; } $unitPrice = (float) ($row['unitPrice'] ?? 0); $key = $this->normalizeBarcode($barcode) . '|' . number_format($unitPrice, 4, '.', ''); $grouped[$key] ??= ['barcode' => $barcode, 'unitPrice' => $unitPrice, 'qty' => 0.0, 'amount' => 0.0]; $grouped[$key]['qty'] += (float) ($row['qty'] ?? 0); $grouped[$key]['amount'] += (float) ($row['amount'] ?? 0); } return $grouped; } /** 去掉条码末尾的「厂家编号 + 版本」后缀,双方口径统一后再匹配。 */ private function normalizeBarcode(string $barcode): string { $normalized = preg_replace(self::SUPPLIER_SUFFIX, '', $barcode); return $normalized === null || $normalized === '' ? $barcode : $normalized; } }