diff --git a/app/Console/Commands/SyncStoreReceiptOrderMatches.php b/app/Console/Commands/SyncStoreReceiptOrderMatches.php index 865f4842..4b1817d0 100644 --- a/app/Console/Commands/SyncStoreReceiptOrderMatches.php +++ b/app/Console/Commands/SyncStoreReceiptOrderMatches.php @@ -366,6 +366,7 @@ class SyncStoreReceiptOrderMatches extends Command // 种菜与备注映射先用正常连接读取,账单游标使用独立的不缓冲连接。 $plantingCores = $this->plantingOrderCores(); $plantingReimbursementCores = $this->plantingReimbursementCores(); + $plantingUnreimbursedCores = $this->plantingUnreimbursedCores(); $remarkMap = app(AccountLedgerRemarkMap::class); $tabPlatform = $remarkMap->defaultPlatform($platform); [ @@ -377,7 +378,7 @@ class SyncStoreReceiptOrderMatches extends Command ] = $this->aggregateBills( $this->streamBillRows($billQuery->orderBy($timeColumn)), $source, $orderColumn, $timeColumn, $amountColumn, $expenseColumn, $realStoreColumn, - $plantingCores, $plantingReimbursementCores, $remarkMap, $tabPlatform, + $plantingCores, $plantingReimbursementCores, $plantingUnreimbursedCores, $remarkMap, $tabPlatform, ); if ($billCount === 0) { $this->warn('该账期没有符合条件的账单记录,未写入任何数据。'); @@ -388,6 +389,8 @@ class SyncStoreReceiptOrderMatches extends Command $plantingRows = 0; $plantingReimbursementTotal = 0.0; $plantingReimbursementRows = 0; + $plantingUnreimbursedTotal = 0.0; + $plantingUnreimbursedRows = 0; $this->info('账单收款笔数:' . $paymentTotal . ',按「店铺 + 订单号」聚合后:' . count($groups) . ' 行'); $this->line(' 优惠金额合计:' . number_format($discountTotal, 2) . ',冲减金额合计:' . number_format($offsetTotal, 2) . ',退款金额合计:' . number_format($refundTotal, 2)); foreach ($groups as $group) { @@ -399,6 +402,10 @@ class SyncStoreReceiptOrderMatches extends Command $plantingReimbursementRows++; $plantingReimbursementTotal += (float) $group['income_amount']; } + if (! empty($group['planting_unreimbursed_seen'])) { + $plantingUnreimbursedRows++; + $plantingUnreimbursedTotal += (float) $group['income_amount']; + } } $this->line(sprintf( ' 种菜金额:%d 行(识别到 %d 个种菜订单号),合计 %s', @@ -411,6 +418,11 @@ class SyncStoreReceiptOrderMatches extends Command $plantingReimbursementRows, number_format($plantingReimbursementTotal, 2), )); + $this->line(sprintf( + ' 种菜未报销:%d 行(按聚水潭种菜标签),合计 %s', + $plantingUnreimbursedRows, + number_format($plantingUnreimbursedTotal, 2), + )); $this->line(sprintf( ' 已挂订单行:优惠 %s / 冲减 %s;未挂到订单行:%s(金额 %s,已补成「无收款账单 / 账单无单号」行)', number_format($attachedDiscount, 2), @@ -764,6 +776,7 @@ class SyncStoreReceiptOrderMatches extends Command 'refund_amount' => ! empty($group['refund_seen']) ? round((float) $group['refund_amount'], 2) : null, 'planting_amount' => ! empty($group['planting_seen']) ? $income : null, 'planting_reimbursement_amount' => ! empty($group['planting_reimbursement_seen']) ? $income : null, + 'planting_unreimbursed_amount' => ! empty($group['planting_unreimbursed_seen']) ? $income : null, 'product_amount' => $productAmount, 'paid_amount' => $paidAmount, 'difference' => $difference, @@ -879,7 +892,7 @@ class SyncStoreReceiptOrderMatches extends Command /** - * 只回填「种菜金额 / 种菜报销」:不读账单,直接按业绩明细里已有的订单号判断,是就写入收入金额。 + * 只回填「种菜金额 / 种菜报销 / 种菜未报销」:不读账单,直接按订单号判断,是就写入收入金额。 */ private function backfillPlantingAmounts(string $reportMonth): int { @@ -890,6 +903,7 @@ class SyncStoreReceiptOrderMatches extends Command $connection = (string) config('upstream_mirrors.connection', 'business_mirror'); $cores = $this->plantingOrderCores(); $reimbursementCores = $this->plantingReimbursementCores(); + $unreimbursedCores = $this->plantingUnreimbursedCores(); if ($cores === []) { $this->warn('没有识别到任何种菜订单号(种菜明细表与聚水潭订单标签明细的交集为空),本列将全部为空。'); } @@ -898,12 +912,18 @@ class SyncStoreReceiptOrderMatches extends Command ->where('report_month', $reportMonth); $rows = $target()->get(['row_key', 'order_core_no', 'income_amount']); - $target()->update(['planting_amount' => null, 'planting_reimbursement_amount' => null]); + $target()->update([ + 'planting_amount' => null, + 'planting_reimbursement_amount' => null, + 'planting_unreimbursed_amount' => null, + ]); $values = []; $total = 0.0; $reimbursementValues = []; $reimbursementTotal = 0.0; + $unreimbursedValues = []; + $unreimbursedTotal = 0.0; foreach ($rows as $row) { $core = self::orderCoreNo((string) $row->order_core_no); if ($core === '') { @@ -919,13 +939,18 @@ class SyncStoreReceiptOrderMatches extends Command $reimbursementValues[(string) $row->row_key] = $amount; $reimbursementTotal += $amount; } + if (isset($unreimbursedCores[$core])) { + $unreimbursedValues[(string) $row->row_key] = $amount; + $unreimbursedTotal += $amount; + } } $this->updateAmountColumn($connection, $reportMonth, 'planting_amount', $values); $this->updateAmountColumn($connection, $reportMonth, 'planting_reimbursement_amount', $reimbursementValues); + $this->updateAmountColumn($connection, $reportMonth, 'planting_unreimbursed_amount', $unreimbursedValues); $this->info(sprintf( - '已回填 %s:种菜金额 %d / %d 行,合计 %s(识别 %d 个种菜订单号);种菜报销 %d 行,合计 %s(识别 %d 个种菜明细订单号)。', + '已回填 %s:种菜金额 %d / %d 行,合计 %s(识别 %d 个种菜订单号);种菜报销 %d 行,合计 %s(识别 %d 个种菜明细订单号);种菜未报销 %d 行,合计 %s(识别 %d 个聚水潭种菜标签订单号)。', $reportMonth, count($values), count($rows), @@ -934,6 +959,9 @@ class SyncStoreReceiptOrderMatches extends Command count($reimbursementValues), number_format($reimbursementTotal, 2), count($reimbursementCores), + count($unreimbursedValues), + number_format($unreimbursedTotal, 2), + count($unreimbursedCores), )); return self::SUCCESS; @@ -974,6 +1002,7 @@ class SyncStoreReceiptOrderMatches extends Command 'refund_amount' => null, 'planting_amount' => null, 'planting_reimbursement_amount' => null, + 'planting_unreimbursed_amount' => null, ]); $discounts = []; @@ -981,12 +1010,14 @@ class SyncStoreReceiptOrderMatches extends Command $refunds = []; $plantings = []; $plantingReimbursements = []; + $plantingUnreimbursed = []; foreach ($groups as $group) { if (empty($group['discount_seen']) && empty($group['offset_seen']) && empty($group['refund_seen']) && empty($group['planting_seen']) - && empty($group['planting_reimbursement_seen'])) { + && empty($group['planting_reimbursement_seen']) + && empty($group['planting_unreimbursed_seen'])) { continue; } @@ -1010,6 +1041,9 @@ class SyncStoreReceiptOrderMatches extends Command if (! empty($group['planting_reimbursement_seen'])) { $plantingReimbursements[$rowKey] = round((float) $group['income_amount'], 2); } + if (! empty($group['planting_unreimbursed_seen'])) { + $plantingUnreimbursed[$rowKey] = round((float) $group['income_amount'], 2); + } } $this->updateAmountColumn($connection, $reportMonth, 'discount_amount', $discounts); @@ -1017,6 +1051,7 @@ class SyncStoreReceiptOrderMatches extends Command $this->updateAmountColumn($connection, $reportMonth, 'refund_amount', $refunds); $this->updateAmountColumn($connection, $reportMonth, 'planting_amount', $plantings); $this->updateAmountColumn($connection, $reportMonth, 'planting_reimbursement_amount', $plantingReimbursements); + $this->updateAmountColumn($connection, $reportMonth, 'planting_unreimbursed_amount', $plantingUnreimbursed); // A 方案:本月本平台的补行(payment_count=0 的行)整体重建,保证幂等。 $target() @@ -1053,7 +1088,7 @@ class SyncStoreReceiptOrderMatches extends Command ]); $this->info(sprintf( - '已回填 %s %s:优惠 %d 行(%s)/ 冲减 %d 行(%s)/ 补「无收款账单/无单号」优惠冲减行 %d 行 / 种菜 %d 行(%s)/ 种菜报销 %d 行(%s),镜像原有 %d 行,未重建业绩明细。', + '已回填 %s %s:优惠 %d 行(%s)/ 冲减 %d 行(%s)/ 补「无收款账单/无单号」优惠冲减行 %d 行 / 种菜 %d 行(%s)/ 种菜报销 %d 行(%s)/ 种菜未报销 %d 行(%s),镜像原有 %d 行,未重建业绩明细。', $month, $platform, count($discounts), @@ -1065,6 +1100,8 @@ class SyncStoreReceiptOrderMatches extends Command number_format(array_sum($plantings), 2), count($plantingReimbursements), number_format(array_sum($plantingReimbursements), 2), + count($plantingUnreimbursed), + number_format(array_sum($plantingUnreimbursed), 2), count($existing), )); @@ -1103,6 +1140,7 @@ class SyncStoreReceiptOrderMatches extends Command * @param array $source * @param array $plantingCores * @param array $plantingReimbursementCores + * @param array $plantingUnreimbursedCores * @return array */ private function aggregateBills( @@ -1115,6 +1153,7 @@ class SyncStoreReceiptOrderMatches extends Command string $realStoreColumn, array $plantingCores, array $plantingReimbursementCores, + array $plantingUnreimbursedCores, AccountLedgerRemarkMap $remarkMap, ?string $tabPlatform, ): array { @@ -1245,6 +1284,9 @@ class SyncStoreReceiptOrderMatches extends Command if (isset($plantingReimbursementCores[$core])) { $groups[$key]['planting_reimbursement_seen'] = true; } + if (isset($plantingUnreimbursedCores[$core])) { + $groups[$key]['planting_unreimbursed_seen'] = true; + } } // 行口径保持不变:仍然是「有收款账单(收入白名单)的店铺 + 订单号」, @@ -1306,6 +1348,7 @@ class SyncStoreReceiptOrderMatches extends Command // 补行没有订单侧商品金额,种菜金额同样留空;列集合与顺序必须与主行完全一致,批量插入才不会错列。 'planting_amount' => null, 'planting_reimbursement_amount' => null, + 'planting_unreimbursed_amount' => null, 'product_amount' => null, 'paid_amount' => null, 'difference' => null, @@ -1411,6 +1454,32 @@ class SyncStoreReceiptOrderMatches extends Command return $cores; } + /** + * 种菜未报销订单号:只取聚水潭订单标签明细里「是否种菜订单=是」的订单号。 + * + * @return array + */ + private function plantingUnreimbursedCores(): array + { + $companyId = (int) $this->option('company'); + $positiveLabels = []; + foreach (app(JstOrderLabelPlantingRules::class)->map($companyId) as $label => $flag) { + if ($flag === '是') { + $positiveLabels[] = $label; + } + } + + $cores = []; + foreach (app(PlantingOrderLabelLookup::class)->orderNumbers($companyId, $positiveLabels) as $orderNo) { + $core = self::orderCoreNo((string) $orderNo); + if ($core !== '') { + $cores[$core] = true; + } + } + + return $cores; + } + private static function orderCoreNo(string $value): string { $value = str_replace("\u{3000}", ' ', trim($value)); diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 7b95b8b0..da3fc98b 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -1810,6 +1810,7 @@ class Pages extends Controller 'refund_amount' => ['refund_amount', '退款金额', 'money'], 'planting_amount' => ['planting_amount', '种菜金额', 'money'], 'planting_reimbursement_amount' => ['planting_reimbursement_amount', '种菜报销', 'money'], + 'planting_unreimbursed_amount' => ['planting_unreimbursed_amount', '种菜未报销', 'money'], 'product_amount' => ['product_amount', '商品金额', 'money'], 'paid_amount' => ['paid_amount', '订单实付金额', 'money'], 'difference' => ['difference', '差额', 'money'], diff --git a/config/business_navigation.php b/config/business_navigation.php index 785e5989..018e4b30 100644 --- a/config/business_navigation.php +++ b/config/business_navigation.php @@ -835,6 +835,7 @@ $navigation = [ ['name' => 'refund_amount', 'label' => '退款金额', 'kind' => 'number'], ['name' => 'planting_amount', 'label' => '种菜金额', 'kind' => 'number'], ['name' => 'planting_reimbursement_amount', 'label' => '种菜报销', 'kind' => 'number'], + ['name' => 'planting_unreimbursed_amount', 'label' => '种菜未报销', 'kind' => 'number'], ['name' => 'product_amount', 'label' => '商品金额', 'kind' => 'number'], ['name' => 'paid_amount', 'label' => '订单实付/实收金额', 'kind' => 'number'], ['name' => 'difference', 'label' => '差额', 'kind' => 'number'], diff --git a/database/migrations/2026_10_20_030000_add_planting_unreimbursed_amount_to_store_receipt_order_match_rows.php b/database/migrations/2026_10_20_030000_add_planting_unreimbursed_amount_to_store_receipt_order_match_rows.php new file mode 100644 index 00000000..4c511bec --- /dev/null +++ b/database/migrations/2026_10_20_030000_add_planting_unreimbursed_amount_to_store_receipt_order_match_rows.php @@ -0,0 +1,46 @@ +connection()); + if (! $schema->hasTable('store_receipt_order_match_rows')) { + return; + } + + if (! $schema->hasColumn('store_receipt_order_match_rows', 'planting_unreimbursed_amount')) { + $schema->table('store_receipt_order_match_rows', function (Blueprint $table): void { + $table->decimal('planting_unreimbursed_amount', 20, 4) + ->nullable() + ->after('planting_reimbursement_amount'); + }); + } + } + + public function down(): void + { + $schema = Schema::connection($this->connection()); + if (! $schema->hasTable('store_receipt_order_match_rows') + || ! $schema->hasColumn('store_receipt_order_match_rows', 'planting_unreimbursed_amount')) { + return; + } + + $schema->table('store_receipt_order_match_rows', function (Blueprint $table): void { + $table->dropColumn('planting_unreimbursed_amount'); + }); + } +};