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