CaiWuXiTong/app/Console/Commands/SyncPlantingDetailsFromWorkOrders.php
qiuxiang ef62cac0d9 种菜明细表:新增按月从聚水潭种菜报销工单同步的命令(带自动排重)
每个月都要手工整理「中心明细表」,改成一条命令:planting-details:sync-work-orders --month=YYYY-MM。

- 口径用 1~8 月已入库的 7 万多行反推验证过:只取「工单类型=种菜报销、状态=已完成」的工单,明细表「日期」= 工单完成日 - 1 天(历史命中 100%),所以某月数据 = 完成时间落在该月次日到下月次日的工单。
- 字段映射(聚水潭只返回 fieldId,不给字段名,这批 id 是拿历史数据逐列对撞出来的):财务售价/财务运费/财务佣金/京喜差价/扣款/财务空包费/财务打款金额 分别对应工单的 任务付款/任务快递费/任务佣金/京喜差价/任务扣款/任务空包费/最终报销金额;负责人、账户、类型、标签、创建人、平台商品ID 也各有对应字段。
- 自动排重:写入前先删掉「本月 + 本次订单号」的已有行,重复执行不会翻倍;--force-clear 才整月覆盖,--dry-run 只看不写。
- 所属利润中心没有工单来源,用表里已有的 店铺→利润中心 映射回填,命中不了的店铺在命令输出里逐个提示。
- 补测试:字段映射 + 日期口径 + 去重 + 利润中心回填 + force-clear。
2026-10-08 09:35:34 +08:00

79 lines
2.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Console\Commands;
use App\Business\PlantingWorkOrderSynchronizer;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Throwable;
class SyncPlantingDetailsFromWorkOrders extends Command
{
protected $signature = 'planting-details:sync-work-orders
{--month= : 月份 YYYY-MM(与 --from/--to 二选一)}
{--from= : 起始日期 YYYY-MM-DD}
{--to= : 结束日期 YYYY-MM-DD}
{--company=1 : 公司 ID}
{--dry-run : 只统计不写库}
{--force-clear : 整月覆盖(连没有对应工单的人工行一起清掉)}';
protected $description = '把聚水潭「种菜报销」工单同步成种菜明细表(日期 = 工单完成日 − 1;按月重复执行不会重复写)';
public function handle(PlantingWorkOrderSynchronizer $synchronizer): int
{
try {
[$from, $to] = $this->window();
$result = $synchronizer->sync(
$from,
$to,
(int) $this->option('company'),
(bool) $this->option('dry-run'),
(bool) $this->option('force-clear'),
);
} catch (Throwable $exception) {
$this->error($exception->getMessage());
return self::FAILURE;
}
$unmatched = $result['unmatched_stores'];
$result['unmatched_stores'] = count($unmatched);
$this->info(json_encode($result, JSON_UNESCAPED_UNICODE));
foreach (array_slice($unmatched, 0, 20, true) as $store => $count) {
$this->warn(sprintf('未匹配利润中心:%s(%d 行)', $store, $count));
}
if (count($unmatched) > 20) {
$this->warn('……还有 ' . (count($unmatched) - 20) . ' 家店铺未匹配到利润中心。');
}
return self::SUCCESS;
}
/** @return array{0:string,1:string} */
private function window(): array
{
$from = trim((string) $this->option('from'));
$to = trim((string) $this->option('to'));
$month = trim((string) $this->option('month'));
if ($month !== '') {
if ($from !== '' || $to !== '') {
throw new InvalidArgumentException('--month 与 --from/--to 只能给一个。');
}
if (preg_match('/^\d{4}-\d{2}$/', $month) !== 1) {
throw new InvalidArgumentException('--month 必须是 YYYY-MM。');
}
return [$month . '-01', date('Y-m-t', strtotime($month . '-01'))];
}
if ($from === '' || $to === '') {
throw new InvalidArgumentException('请给 --month=YYYY-MM,或同时给 --from 与 --to。');
}
return [$from, $to];
}
}