CaiWuXiTong/app/Console/Commands/AutoMonthlyWeightedAverageAndProductCost.php

134 lines
4.9 KiB
PHP

<?php
namespace App\Console\Commands;
use Carbon\CarbonImmutable;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
/**
* 固定月度顺序:先生成商品月加权平均单价,再同步商品成本表。
*/
class AutoMonthlyWeightedAverageAndProductCost extends Command
{
protected $signature = 'finance:monthly-weighted-product-cost:auto
{--month= : 指定月份;默认取最新已成功的进销存月份}
{--result-json= : 将两阶段结果写入指定 JSON 文件}';
protected $description = '先计算商品月加权平均单价,再自动同步商品成本表';
public function handle(): int
{
$connection = (string) config('upstream_mirrors.connection', 'business_mirror');
$month = $this->targetMonth($connection);
if ($month === null) {
return $this->writeResult([
'status' => 'blocked',
'message' => '未找到已成功导入的进销存月份。',
]);
}
$weightedPath = tempnam(sys_get_temp_dir(), 'weighted-result-') . '.json';
$costPath = tempnam(sys_get_temp_dir(), 'product-cost-result-') . '.json';
try {
$weightedExit = $this->call('product:monthly-weighted-average:auto', [
'--month' => $month->format('Y-m'),
'--result-json' => $weightedPath,
]);
$weighted = $this->readJson($weightedPath);
if (
$weightedExit !== self::SUCCESS
|| in_array($weighted['status'] ?? 'failed', ['blocked', 'failed'], true)
) {
return $this->writeResult([
'status' => 'failed',
'report_month' => $month->toDateString(),
'message' => '商品月加权平均单价未完成,商品成本未执行。',
'weighted_average' => $weighted,
'product_cost' => null,
]);
}
$costExit = $this->call('store-receipt:product-cost:sync', [
'month' => $month->format('Y-m'),
'--result-json' => $costPath,
]);
$cost = $this->readJson($costPath);
$status = $costExit !== self::SUCCESS
? 'failed'
: ((int) ($cost['pending_cost_rows'] ?? 0) > 0 ? 'review_required' : 'success');
return $this->writeResult([
'status' => $status,
'report_month' => $month->toDateString(),
'message' => match ($status) {
'success' => '月加权平均单价与商品成本已按顺序完成。',
'review_required' => '月加权平均单价与商品成本已完成,存在待核验成本。',
default => '商品成本同步失败。',
},
'weighted_average' => $weighted,
'product_cost' => $cost,
]);
} finally {
@unlink($weightedPath);
@unlink($costPath);
}
}
private function targetMonth(string $connection): ?CarbonImmutable
{
$requested = trim((string) $this->option('month'));
if ($requested !== '') {
return CarbonImmutable::parse($requested)->startOfMonth();
}
$latest = DB::connection($connection)->table('inventory_report_authoritative_months')
->where('dataset_key', 'jiansun.inventory-report.authoritative-month')
->where('status', 'succeeded')
->whereNotNull('active_generation')
->orderByDesc('source_month')
->value('source_month');
return $latest === null ? null : CarbonImmutable::parse((string) $latest)->startOfMonth();
}
/** @return array<string,mixed> */
private function readJson(string $path): array
{
if (! is_file($path)) {
return ['status' => 'failed', 'message' => '子任务未生成结果文件。'];
}
$decoded = json_decode((string) file_get_contents($path), true);
return is_array($decoded)
? $decoded
: ['status' => 'failed', 'message' => '子任务结果文件无法解析。'];
}
/** @param array<string,mixed> $result */
private function writeResult(array $result): int
{
$path = trim((string) $this->option('result-json'));
if ($path !== '') {
File::ensureDirectoryExists(dirname($path));
File::put(
$path,
json_encode(
$result,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT,
) . PHP_EOL,
);
}
if (isset($result['message'])) {
$this->line((string) $result['message']);
}
return in_array($result['status'] ?? 'failed', ['success', 'review_required'], true)
? self::SUCCESS
: self::FAILURE;
}
}