103 lines
3.4 KiB
PHP
103 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace Modules\EcommerceFinance\Services;
|
|
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Modules\EcommerceFinance\Repositories\BusinessInformationMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\StoreReportRepository;
|
|
use Throwable;
|
|
|
|
/**
|
|
* 内部流转预计销量取自主体维度经营报表的上月业绩。
|
|
*/
|
|
class InternalFlowProjection
|
|
{
|
|
public const BUYER_INVOICE_RATE = 0.20;
|
|
|
|
public const INTERNAL_INVOICE_RATE = 0.25;
|
|
|
|
public const INPUT_COST_RATE = 0.80;
|
|
|
|
public function __construct(
|
|
private readonly BusinessInformationMirrorRepository $businessInformation,
|
|
private readonly StoreReportRepository $storeReports,
|
|
) {}
|
|
|
|
/**
|
|
* @return array{
|
|
* forecast_month:string,
|
|
* forecast_month_label:string,
|
|
* source_month:string,
|
|
* source_month_label:string,
|
|
* sales_by_company:array<string,float|null>
|
|
* }
|
|
*/
|
|
public function forMonth(?CarbonImmutable $asOf = null): array
|
|
{
|
|
$forecastMonth = ($asOf ?? CarbonImmutable::now())->startOfMonth();
|
|
$sourceMonth = $forecastMonth->subMonthNoOverflow();
|
|
$cacheKey = 'internal-flow-projection:v1:' . $sourceMonth->format('Y-m');
|
|
$resolve = fn (): array => $this->build($forecastMonth, $sourceMonth);
|
|
|
|
try {
|
|
return Cache::remember($cacheKey, now()->addMinutes(10), $resolve);
|
|
} catch (Throwable $exception) {
|
|
report($exception);
|
|
|
|
return $resolve();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array{
|
|
* forecast_month:string,
|
|
* forecast_month_label:string,
|
|
* source_month:string,
|
|
* source_month_label:string,
|
|
* sales_by_company:array<string,float|null>
|
|
* }
|
|
*/
|
|
private function build(CarbonImmutable $forecastMonth, CarbonImmutable $sourceMonth): array
|
|
{
|
|
$stores = $this->businessInformation->receivingAccountStores();
|
|
foreach ($stores as &$store) {
|
|
// 标记系统主体已知,避免 StoreReportPeriodBuilder 再次按店铺批量查询主体。
|
|
$store['system_company_name'] = $store['company_name'] ?? null;
|
|
}
|
|
unset($store);
|
|
$rows = $this->storeReports->rows($stores, $sourceMonth->year, []);
|
|
$incomeKey = sprintf('income_%02d', $sourceMonth->month);
|
|
$totals = [];
|
|
|
|
foreach ($rows as $row) {
|
|
$entity = trim((string) ($row['entity'] ?? ''));
|
|
if ($entity === '') {
|
|
continue;
|
|
}
|
|
|
|
$totals[$entity] ??= ['has_value' => false, 'amount' => 0.0];
|
|
$value = $row[$incomeKey] ?? null;
|
|
if ($value === null || $value === '') {
|
|
continue;
|
|
}
|
|
|
|
$totals[$entity]['has_value'] = true;
|
|
$totals[$entity]['amount'] += (float) $value;
|
|
}
|
|
|
|
return [
|
|
'forecast_month' => $forecastMonth->format('Y-m'),
|
|
'forecast_month_label' => $forecastMonth->format('n') . '月',
|
|
'source_month' => $sourceMonth->format('Y-m'),
|
|
'source_month_label' => $sourceMonth->format('n') . '月',
|
|
'sales_by_company' => array_map(
|
|
static fn (array $total): ?float => $total['has_value']
|
|
? round((float) $total['amount'], 6)
|
|
: null,
|
|
$totals,
|
|
),
|
|
];
|
|
}
|
|
}
|