业绩明细新增种菜报销列

This commit is contained in:
qiuxiang 2026-10-20 10:36:24 +08:00
parent cdabffb3a6
commit 4511f6c71a
4 changed files with 121 additions and 15 deletions

View File

@ -365,6 +365,7 @@ class SyncStoreReceiptOrderMatches extends Command
// 种菜与备注映射先用正常连接读取,账单游标使用独立的不缓冲连接。
$plantingCores = $this->plantingOrderCores();
$plantingReimbursementCores = $this->plantingReimbursementCores();
$remarkMap = app(AccountLedgerRemarkMap::class);
$tabPlatform = $remarkMap->defaultPlatform($platform);
[
@ -376,7 +377,7 @@ class SyncStoreReceiptOrderMatches extends Command
] = $this->aggregateBills(
$this->streamBillRows($billQuery->orderBy($timeColumn)), $source,
$orderColumn, $timeColumn, $amountColumn, $expenseColumn, $realStoreColumn,
$plantingCores, $remarkMap, $tabPlatform,
$plantingCores, $plantingReimbursementCores, $remarkMap, $tabPlatform,
);
if ($billCount === 0) {
$this->warn('该账期没有符合条件的账单记录,未写入任何数据。');
@ -385,6 +386,8 @@ class SyncStoreReceiptOrderMatches extends Command
}
$plantingTotal = 0.0;
$plantingRows = 0;
$plantingReimbursementTotal = 0.0;
$plantingReimbursementRows = 0;
$this->info('账单收款笔数:' . $paymentTotal . ',按「店铺 + 订单号」聚合后:' . count($groups) . ' 行');
$this->line(' 优惠金额合计:' . number_format($discountTotal, 2) . ',冲减金额合计:' . number_format($offsetTotal, 2) . ',退款金额合计:' . number_format($refundTotal, 2));
foreach ($groups as $group) {
@ -392,6 +395,10 @@ class SyncStoreReceiptOrderMatches extends Command
$plantingRows++;
$plantingTotal += (float) $group['income_amount'];
}
if (! empty($group['planting_reimbursement_seen'])) {
$plantingReimbursementRows++;
$plantingReimbursementTotal += (float) $group['income_amount'];
}
}
$this->line(sprintf(
' 种菜金额:%d 行(识别到 %d 个种菜订单号),合计 %s',
@ -399,6 +406,11 @@ class SyncStoreReceiptOrderMatches extends Command
count($plantingCores),
number_format($plantingTotal, 2),
));
$this->line(sprintf(
' 种菜报销:%d 行(按种菜明细表订单号),合计 %s',
$plantingReimbursementRows,
number_format($plantingReimbursementTotal, 2),
));
$this->line(sprintf(
' 已挂订单行:优惠 %s / 冲减 %s;未挂到订单行:%s(金额 %s,已补成「无收款账单 / 账单无单号」行)',
number_format($attachedDiscount, 2),
@ -751,6 +763,7 @@ class SyncStoreReceiptOrderMatches extends Command
'offset_amount' => ! empty($group['offset_seen']) ? round((float) $group['offset_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_reimbursement_amount' => ! empty($group['planting_reimbursement_seen']) ? $income : null,
'product_amount' => $productAmount,
'paid_amount' => $paidAmount,
'difference' => $difference,
@ -866,8 +879,7 @@ class SyncStoreReceiptOrderMatches extends Command
/**
* 只回填「种菜金额」:不读账单,直接按业绩明细里已有的「订单号」判断是不是种菜订单,
* 是就把该行的收入金额写进去,不是就清空。适合给已经同步好的账期补这一列。
* 只回填「种菜金额 / 种菜报销」:不读账单,直接按业绩明细里已有的订单号判断,是就写入收入金额。
*/
private function backfillPlantingAmounts(string $reportMonth): int
{
@ -877,6 +889,7 @@ class SyncStoreReceiptOrderMatches extends Command
}
$connection = (string) config('upstream_mirrors.connection', 'business_mirror');
$cores = $this->plantingOrderCores();
$reimbursementCores = $this->plantingReimbursementCores();
if ($cores === []) {
$this->warn('没有识别到任何种菜订单号(种菜明细表与聚水潭订单标签明细的交集为空),本列将全部为空。');
}
@ -885,30 +898,42 @@ class SyncStoreReceiptOrderMatches extends Command
->where('report_month', $reportMonth);
$rows = $target()->get(['row_key', 'order_core_no', 'income_amount']);
$target()->update(['planting_amount' => null]);
$target()->update(['planting_amount' => null, 'planting_reimbursement_amount' => null]);
$values = [];
$total = 0.0;
$reimbursementValues = [];
$reimbursementTotal = 0.0;
foreach ($rows as $row) {
$core = self::orderCoreNo((string) $row->order_core_no);
if ($core === '' || ! isset($cores[$core])) {
if ($core === '') {
continue;
}
$amount = round((float) $row->income_amount, 2);
if (isset($cores[$core])) {
$values[(string) $row->row_key] = $amount;
$total += $amount;
}
if (isset($reimbursementCores[$core])) {
$reimbursementValues[(string) $row->row_key] = $amount;
$reimbursementTotal += $amount;
}
}
$this->updateAmountColumn($connection, $reportMonth, 'planting_amount', $values);
$this->updateAmountColumn($connection, $reportMonth, 'planting_reimbursement_amount', $reimbursementValues);
$this->info(sprintf(
'已回填 %s 种菜金额:%d / %d 行,合计 %s(共识别 %d 个种菜订单号)。',
'已回填 %s:种菜金额 %d / %d 行,合计 %s(识别 %d 个种菜订单号);种菜报销 %d 行,合计 %s(识别 %d 个种菜明细订单号)。',
$reportMonth,
count($values),
count($rows),
number_format($total, 2),
count($cores),
count($reimbursementValues),
number_format($reimbursementTotal, 2),
count($reimbursementCores),
));
return self::SUCCESS;
@ -943,14 +968,25 @@ class SyncStoreReceiptOrderMatches extends Command
$target()
->where('report_month', $reportMonth)
->where('platform', $platform)
->update(['discount_amount' => null, 'offset_amount' => null, 'refund_amount' => null, 'planting_amount' => null]);
->update([
'discount_amount' => null,
'offset_amount' => null,
'refund_amount' => null,
'planting_amount' => null,
'planting_reimbursement_amount' => null,
]);
$discounts = [];
$offsets = [];
$refunds = [];
$plantings = [];
$plantingReimbursements = [];
foreach ($groups as $group) {
if (empty($group['discount_seen']) && empty($group['offset_seen']) && empty($group['refund_seen']) && empty($group['planting_seen'])) {
if (empty($group['discount_seen'])
&& empty($group['offset_seen'])
&& empty($group['refund_seen'])
&& empty($group['planting_seen'])
&& empty($group['planting_reimbursement_seen'])) {
continue;
}
@ -971,12 +1007,16 @@ class SyncStoreReceiptOrderMatches extends Command
if (! empty($group['planting_seen'])) {
$plantings[$rowKey] = round((float) $group['income_amount'], 2);
}
if (! empty($group['planting_reimbursement_seen'])) {
$plantingReimbursements[$rowKey] = round((float) $group['income_amount'], 2);
}
}
$this->updateAmountColumn($connection, $reportMonth, 'discount_amount', $discounts);
$this->updateAmountColumn($connection, $reportMonth, 'offset_amount', $offsets);
$this->updateAmountColumn($connection, $reportMonth, 'refund_amount', $refunds);
$this->updateAmountColumn($connection, $reportMonth, 'planting_amount', $plantings);
$this->updateAmountColumn($connection, $reportMonth, 'planting_reimbursement_amount', $plantingReimbursements);
// A 方案:本月本平台的补行(payment_count=0 的行)整体重建,保证幂等。
$target()
@ -1013,7 +1053,7 @@ class SyncStoreReceiptOrderMatches extends Command
]);
$this->info(sprintf(
'已回填 %s %s:优惠 %d 行(%s)/ 冲减 %d 行(%s)/ 补「无收款账单/无单号」优惠冲减行 %d 行 / 种菜 %d 行(%s),镜像原有 %d 行,未重建业绩明细。',
'已回填 %s %s:优惠 %d 行(%s)/ 冲减 %d 行(%s)/ 补「无收款账单/无单号」优惠冲减行 %d 行 / 种菜 %d 行(%s)/ 种菜报销 %d 行(%s),镜像原有 %d 行,未重建业绩明细。',
$month,
$platform,
count($discounts),
@ -1023,6 +1063,8 @@ class SyncStoreReceiptOrderMatches extends Command
$promoRows,
count($plantings),
number_format(array_sum($plantings), 2),
count($plantingReimbursements),
number_format(array_sum($plantingReimbursements), 2),
count($existing),
));
@ -1060,6 +1102,7 @@ class SyncStoreReceiptOrderMatches extends Command
* @param iterable<int,object> $bills
* @param array<string,mixed> $source
* @param array<string,bool> $plantingCores
* @param array<string,bool> $plantingReimbursementCores
* @return array<string,mixed>
*/
private function aggregateBills(
@ -1071,6 +1114,7 @@ class SyncStoreReceiptOrderMatches extends Command
string $expenseColumn,
string $realStoreColumn,
array $plantingCores,
array $plantingReimbursementCores,
AccountLedgerRemarkMap $remarkMap,
?string $tabPlatform,
): array {
@ -1198,6 +1242,9 @@ class SyncStoreReceiptOrderMatches extends Command
if (isset($plantingCores[$core])) {
$groups[$key]['planting_seen'] = true;
}
if (isset($plantingReimbursementCores[$core])) {
$groups[$key]['planting_reimbursement_seen'] = true;
}
}
// 行口径保持不变:仍然是「有收款账单(收入白名单)的店铺 + 订单号」,
@ -1258,6 +1305,7 @@ class SyncStoreReceiptOrderMatches extends Command
'refund_amount' => ! empty($group['refund_seen']) ? round((float) $group['refund_amount'], 2) : null,
// 补行没有订单侧商品金额,种菜金额同样留空;列集合与顺序必须与主行完全一致,批量插入才不会错列。
'planting_amount' => null,
'planting_reimbursement_amount' => null,
'product_amount' => null,
'paid_amount' => null,
'difference' => null,
@ -1324,6 +1372,7 @@ class SyncStoreReceiptOrderMatches extends Command
private function plantingOrderCores(): array
{
$companyId = (int) $this->option('company');
$cores = $this->plantingReimbursementCores();
$positiveLabels = [];
foreach (app(JstOrderLabelPlantingRules::class)->map($companyId) as $label => $flag) {
if ($flag === '是') {
@ -1331,9 +1380,7 @@ class SyncStoreReceiptOrderMatches extends Command
}
}
$cores = [];
// 并集的第一部分:聚水潭订单标签明细里「是否种菜订单=是」的订单号
// 聚水潭订单标签明细里「是否种菜订单=是」的订单号
// (命中种菜标签映射,或货号含信誉订单虚拟条码 X00006,与页面同一规则)
foreach (app(PlantingOrderLabelLookup::class)->orderNumbers($companyId, $positiveLabels) as $orderNo) {
$core = self::orderCoreNo((string) $orderNo);
@ -1342,7 +1389,18 @@ class SyncStoreReceiptOrderMatches extends Command
}
}
// 并集的第二部分:种菜明细表里的订单号
return $cores;
}
/**
* 种菜报销订单号:只取「种菜明细表」的订单号,不包含聚水潭种菜标签。
*
* @return array<string,true>
*/
private function plantingReimbursementCores(): array
{
$companyId = (int) $this->option('company');
$cores = [];
foreach (DB::table('planting_details')->where('company_id', $companyId)->distinct()->pluck('order_no') as $orderNo) {
$core = self::orderCoreNo((string) $orderNo);
if ($core !== '') {

View File

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

View File

@ -834,6 +834,7 @@ $navigation = [
['name' => 'offset_amount', 'label' => '冲减金额', 'kind' => 'number'],
['name' => 'refund_amount', 'label' => '退款金额', 'kind' => 'number'],
['name' => 'planting_amount', 'label' => '种菜金额', 'kind' => 'number'],
['name' => 'planting_reimbursement_amount', 'label' => '种菜报销', 'kind' => 'number'],
['name' => 'product_amount', 'label' => '商品金额', 'kind' => 'number'],
['name' => 'paid_amount', '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_reimbursement_amount')) {
$schema->table('store_receipt_order_match_rows', function (Blueprint $table): void {
$table->decimal('planting_reimbursement_amount', 20, 4)
->nullable()
->after('planting_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_reimbursement_amount')) {
return;
}
$schema->table('store_receipt_order_match_rows', function (Blueprint $table): void {
$table->dropColumn('planting_reimbursement_amount');
});
}
};