业绩明细新增种菜未报销列

This commit is contained in:
qiuxiang 2026-10-20 10:55:33 +08:00
parent 75aac2e5aa
commit ffa4bbf15f
4 changed files with 123 additions and 6 deletions

View File

@ -366,6 +366,7 @@ class SyncStoreReceiptOrderMatches extends Command
// 种菜与备注映射先用正常连接读取,账单游标使用独立的不缓冲连接。 // 种菜与备注映射先用正常连接读取,账单游标使用独立的不缓冲连接。
$plantingCores = $this->plantingOrderCores(); $plantingCores = $this->plantingOrderCores();
$plantingReimbursementCores = $this->plantingReimbursementCores(); $plantingReimbursementCores = $this->plantingReimbursementCores();
$plantingUnreimbursedCores = $this->plantingUnreimbursedCores();
$remarkMap = app(AccountLedgerRemarkMap::class); $remarkMap = app(AccountLedgerRemarkMap::class);
$tabPlatform = $remarkMap->defaultPlatform($platform); $tabPlatform = $remarkMap->defaultPlatform($platform);
[ [
@ -377,7 +378,7 @@ class SyncStoreReceiptOrderMatches extends Command
] = $this->aggregateBills( ] = $this->aggregateBills(
$this->streamBillRows($billQuery->orderBy($timeColumn)), $source, $this->streamBillRows($billQuery->orderBy($timeColumn)), $source,
$orderColumn, $timeColumn, $amountColumn, $expenseColumn, $realStoreColumn, $orderColumn, $timeColumn, $amountColumn, $expenseColumn, $realStoreColumn,
$plantingCores, $plantingReimbursementCores, $remarkMap, $tabPlatform, $plantingCores, $plantingReimbursementCores, $plantingUnreimbursedCores, $remarkMap, $tabPlatform,
); );
if ($billCount === 0) { if ($billCount === 0) {
$this->warn('该账期没有符合条件的账单记录,未写入任何数据。'); $this->warn('该账期没有符合条件的账单记录,未写入任何数据。');
@ -388,6 +389,8 @@ class SyncStoreReceiptOrderMatches extends Command
$plantingRows = 0; $plantingRows = 0;
$plantingReimbursementTotal = 0.0; $plantingReimbursementTotal = 0.0;
$plantingReimbursementRows = 0; $plantingReimbursementRows = 0;
$plantingUnreimbursedTotal = 0.0;
$plantingUnreimbursedRows = 0;
$this->info('账单收款笔数:' . $paymentTotal . ',按「店铺 + 订单号」聚合后:' . count($groups) . ' 行'); $this->info('账单收款笔数:' . $paymentTotal . ',按「店铺 + 订单号」聚合后:' . count($groups) . ' 行');
$this->line(' 优惠金额合计:' . number_format($discountTotal, 2) . ',冲减金额合计:' . number_format($offsetTotal, 2) . ',退款金额合计:' . number_format($refundTotal, 2)); $this->line(' 优惠金额合计:' . number_format($discountTotal, 2) . ',冲减金额合计:' . number_format($offsetTotal, 2) . ',退款金额合计:' . number_format($refundTotal, 2));
foreach ($groups as $group) { foreach ($groups as $group) {
@ -399,6 +402,10 @@ class SyncStoreReceiptOrderMatches extends Command
$plantingReimbursementRows++; $plantingReimbursementRows++;
$plantingReimbursementTotal += (float) $group['income_amount']; $plantingReimbursementTotal += (float) $group['income_amount'];
} }
if (! empty($group['planting_unreimbursed_seen'])) {
$plantingUnreimbursedRows++;
$plantingUnreimbursedTotal += (float) $group['income_amount'];
}
} }
$this->line(sprintf( $this->line(sprintf(
' 种菜金额:%d 行(识别到 %d 个种菜订单号),合计 %s', ' 种菜金额:%d 行(识别到 %d 个种菜订单号),合计 %s',
@ -411,6 +418,11 @@ class SyncStoreReceiptOrderMatches extends Command
$plantingReimbursementRows, $plantingReimbursementRows,
number_format($plantingReimbursementTotal, 2), number_format($plantingReimbursementTotal, 2),
)); ));
$this->line(sprintf(
' 种菜未报销:%d 行(按聚水潭种菜标签),合计 %s',
$plantingUnreimbursedRows,
number_format($plantingUnreimbursedTotal, 2),
));
$this->line(sprintf( $this->line(sprintf(
' 已挂订单行:优惠 %s / 冲减 %s;未挂到订单行:%s(金额 %s,已补成「无收款账单 / 账单无单号」行)', ' 已挂订单行:优惠 %s / 冲减 %s;未挂到订单行:%s(金额 %s,已补成「无收款账单 / 账单无单号」行)',
number_format($attachedDiscount, 2), 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, 'refund_amount' => ! empty($group['refund_seen']) ? round((float) $group['refund_amount'], 2) : null,
'planting_amount' => ! empty($group['planting_seen']) ? $income : null, 'planting_amount' => ! empty($group['planting_seen']) ? $income : null,
'planting_reimbursement_amount' => ! empty($group['planting_reimbursement_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, 'product_amount' => $productAmount,
'paid_amount' => $paidAmount, 'paid_amount' => $paidAmount,
'difference' => $difference, 'difference' => $difference,
@ -879,7 +892,7 @@ class SyncStoreReceiptOrderMatches extends Command
/** /**
* 只回填「种菜金额 / 种菜报销」:不读账单,直接按业绩明细里已有的订单号判断,是就写入收入金额。 * 只回填「种菜金额 / 种菜报销 / 种菜未报销」:不读账单,直接按订单号判断,是就写入收入金额。
*/ */
private function backfillPlantingAmounts(string $reportMonth): int private function backfillPlantingAmounts(string $reportMonth): int
{ {
@ -890,6 +903,7 @@ class SyncStoreReceiptOrderMatches extends Command
$connection = (string) config('upstream_mirrors.connection', 'business_mirror'); $connection = (string) config('upstream_mirrors.connection', 'business_mirror');
$cores = $this->plantingOrderCores(); $cores = $this->plantingOrderCores();
$reimbursementCores = $this->plantingReimbursementCores(); $reimbursementCores = $this->plantingReimbursementCores();
$unreimbursedCores = $this->plantingUnreimbursedCores();
if ($cores === []) { if ($cores === []) {
$this->warn('没有识别到任何种菜订单号(种菜明细表与聚水潭订单标签明细的交集为空),本列将全部为空。'); $this->warn('没有识别到任何种菜订单号(种菜明细表与聚水潭订单标签明细的交集为空),本列将全部为空。');
} }
@ -898,12 +912,18 @@ class SyncStoreReceiptOrderMatches extends Command
->where('report_month', $reportMonth); ->where('report_month', $reportMonth);
$rows = $target()->get(['row_key', 'order_core_no', 'income_amount']); $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 = []; $values = [];
$total = 0.0; $total = 0.0;
$reimbursementValues = []; $reimbursementValues = [];
$reimbursementTotal = 0.0; $reimbursementTotal = 0.0;
$unreimbursedValues = [];
$unreimbursedTotal = 0.0;
foreach ($rows as $row) { foreach ($rows as $row) {
$core = self::orderCoreNo((string) $row->order_core_no); $core = self::orderCoreNo((string) $row->order_core_no);
if ($core === '') { if ($core === '') {
@ -919,13 +939,18 @@ class SyncStoreReceiptOrderMatches extends Command
$reimbursementValues[(string) $row->row_key] = $amount; $reimbursementValues[(string) $row->row_key] = $amount;
$reimbursementTotal += $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_amount', $values);
$this->updateAmountColumn($connection, $reportMonth, 'planting_reimbursement_amount', $reimbursementValues); $this->updateAmountColumn($connection, $reportMonth, 'planting_reimbursement_amount', $reimbursementValues);
$this->updateAmountColumn($connection, $reportMonth, 'planting_unreimbursed_amount', $unreimbursedValues);
$this->info(sprintf( $this->info(sprintf(
'已回填 %s:种菜金额 %d / %d 行,合计 %s(识别 %d 个种菜订单号);种菜报销 %d 行,合计 %s(识别 %d 个种菜明细订单号)。', '已回填 %s:种菜金额 %d / %d 行,合计 %s(识别 %d 个种菜订单号);种菜报销 %d 行,合计 %s(识别 %d 个种菜明细订单号);种菜未报销 %d 行,合计 %s(识别 %d 个聚水潭种菜标签订单号)。',
$reportMonth, $reportMonth,
count($values), count($values),
count($rows), count($rows),
@ -934,6 +959,9 @@ class SyncStoreReceiptOrderMatches extends Command
count($reimbursementValues), count($reimbursementValues),
number_format($reimbursementTotal, 2), number_format($reimbursementTotal, 2),
count($reimbursementCores), count($reimbursementCores),
count($unreimbursedValues),
number_format($unreimbursedTotal, 2),
count($unreimbursedCores),
)); ));
return self::SUCCESS; return self::SUCCESS;
@ -974,6 +1002,7 @@ class SyncStoreReceiptOrderMatches extends Command
'refund_amount' => null, 'refund_amount' => null,
'planting_amount' => null, 'planting_amount' => null,
'planting_reimbursement_amount' => null, 'planting_reimbursement_amount' => null,
'planting_unreimbursed_amount' => null,
]); ]);
$discounts = []; $discounts = [];
@ -981,12 +1010,14 @@ class SyncStoreReceiptOrderMatches extends Command
$refunds = []; $refunds = [];
$plantings = []; $plantings = [];
$plantingReimbursements = []; $plantingReimbursements = [];
$plantingUnreimbursed = [];
foreach ($groups as $group) { foreach ($groups as $group) {
if (empty($group['discount_seen']) if (empty($group['discount_seen'])
&& empty($group['offset_seen']) && empty($group['offset_seen'])
&& empty($group['refund_seen']) && empty($group['refund_seen'])
&& empty($group['planting_seen']) && empty($group['planting_seen'])
&& empty($group['planting_reimbursement_seen'])) { && empty($group['planting_reimbursement_seen'])
&& empty($group['planting_unreimbursed_seen'])) {
continue; continue;
} }
@ -1010,6 +1041,9 @@ class SyncStoreReceiptOrderMatches extends Command
if (! empty($group['planting_reimbursement_seen'])) { if (! empty($group['planting_reimbursement_seen'])) {
$plantingReimbursements[$rowKey] = round((float) $group['income_amount'], 2); $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); $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, 'refund_amount', $refunds);
$this->updateAmountColumn($connection, $reportMonth, 'planting_amount', $plantings); $this->updateAmountColumn($connection, $reportMonth, 'planting_amount', $plantings);
$this->updateAmountColumn($connection, $reportMonth, 'planting_reimbursement_amount', $plantingReimbursements); $this->updateAmountColumn($connection, $reportMonth, 'planting_reimbursement_amount', $plantingReimbursements);
$this->updateAmountColumn($connection, $reportMonth, 'planting_unreimbursed_amount', $plantingUnreimbursed);
// A 方案:本月本平台的补行(payment_count=0 的行)整体重建,保证幂等。 // A 方案:本月本平台的补行(payment_count=0 的行)整体重建,保证幂等。
$target() $target()
@ -1053,7 +1088,7 @@ class SyncStoreReceiptOrderMatches extends Command
]); ]);
$this->info(sprintf( $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, $month,
$platform, $platform,
count($discounts), count($discounts),
@ -1065,6 +1100,8 @@ class SyncStoreReceiptOrderMatches extends Command
number_format(array_sum($plantings), 2), number_format(array_sum($plantings), 2),
count($plantingReimbursements), count($plantingReimbursements),
number_format(array_sum($plantingReimbursements), 2), number_format(array_sum($plantingReimbursements), 2),
count($plantingUnreimbursed),
number_format(array_sum($plantingUnreimbursed), 2),
count($existing), count($existing),
)); ));
@ -1103,6 +1140,7 @@ class SyncStoreReceiptOrderMatches extends Command
* @param array<string,mixed> $source * @param array<string,mixed> $source
* @param array<string,bool> $plantingCores * @param array<string,bool> $plantingCores
* @param array<string,bool> $plantingReimbursementCores * @param array<string,bool> $plantingReimbursementCores
* @param array<string,bool> $plantingUnreimbursedCores
* @return array<string,mixed> * @return array<string,mixed>
*/ */
private function aggregateBills( private function aggregateBills(
@ -1115,6 +1153,7 @@ class SyncStoreReceiptOrderMatches extends Command
string $realStoreColumn, string $realStoreColumn,
array $plantingCores, array $plantingCores,
array $plantingReimbursementCores, array $plantingReimbursementCores,
array $plantingUnreimbursedCores,
AccountLedgerRemarkMap $remarkMap, AccountLedgerRemarkMap $remarkMap,
?string $tabPlatform, ?string $tabPlatform,
): array { ): array {
@ -1245,6 +1284,9 @@ class SyncStoreReceiptOrderMatches extends Command
if (isset($plantingReimbursementCores[$core])) { if (isset($plantingReimbursementCores[$core])) {
$groups[$key]['planting_reimbursement_seen'] = true; $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_amount' => null,
'planting_reimbursement_amount' => null, 'planting_reimbursement_amount' => null,
'planting_unreimbursed_amount' => null,
'product_amount' => null, 'product_amount' => null,
'paid_amount' => null, 'paid_amount' => null,
'difference' => null, 'difference' => null,
@ -1411,6 +1454,32 @@ class SyncStoreReceiptOrderMatches extends Command
return $cores; return $cores;
} }
/**
* 种菜未报销订单号:只取聚水潭订单标签明细里「是否种菜订单=是」的订单号。
*
* @return array<string,true>
*/
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 private static function orderCoreNo(string $value): string
{ {
$value = str_replace("\u{3000}", ' ', trim($value)); $value = str_replace("\u{3000}", ' ', trim($value));

View File

@ -1810,6 +1810,7 @@ class Pages extends Controller
'refund_amount' => ['refund_amount', '退款金额', 'money'], 'refund_amount' => ['refund_amount', '退款金额', 'money'],
'planting_amount' => ['planting_amount', '种菜金额', 'money'], 'planting_amount' => ['planting_amount', '种菜金额', 'money'],
'planting_reimbursement_amount' => ['planting_reimbursement_amount', '种菜报销', 'money'], 'planting_reimbursement_amount' => ['planting_reimbursement_amount', '种菜报销', 'money'],
'planting_unreimbursed_amount' => ['planting_unreimbursed_amount', '种菜未报销', 'money'],
'product_amount' => ['product_amount', '商品金额', 'money'], 'product_amount' => ['product_amount', '商品金额', 'money'],
'paid_amount' => ['paid_amount', '订单实付金额', 'money'], 'paid_amount' => ['paid_amount', '订单实付金额', 'money'],
'difference' => ['difference', '差额', 'money'], 'difference' => ['difference', '差额', 'money'],

View File

@ -835,6 +835,7 @@ $navigation = [
['name' => 'refund_amount', 'label' => '退款金额', 'kind' => 'number'], ['name' => 'refund_amount', 'label' => '退款金额', 'kind' => 'number'],
['name' => 'planting_amount', 'label' => '种菜金额', 'kind' => 'number'], ['name' => 'planting_amount', 'label' => '种菜金额', 'kind' => 'number'],
['name' => 'planting_reimbursement_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' => 'product_amount', 'label' => '商品金额', 'kind' => 'number'],
['name' => 'paid_amount', 'label' => '订单实付/实收金额', 'kind' => 'number'], ['name' => 'paid_amount', 'label' => '订单实付/实收金额', 'kind' => 'number'],
['name' => 'difference', 'label' => '差额', 'kind' => 'number'], ['name' => 'difference', 'label' => '差额', 'kind' => 'number'],

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* 业绩明细新增「种菜未报销」列(放在种菜报销后面):
* 仅按聚水潭订单标签明细「是否种菜订单=是」的订单号命中,金额取收入金额。
*/
return new class extends Migration
{
private function connection(): string
{
return (string) config('upstream_mirrors.connection', 'business_mirror');
}
public function up(): void
{
$schema = Schema::connection($this->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');
});
}
};