116 lines
4.6 KiB
PHP
116 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace Modules\EcommerceFinance\Services;
|
|
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Str;
|
|
use Modules\EcommerceFinance\Repositories\ProductPurchasePriceMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\ProductPurchasePriceRepository;
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
class ProductPurchasePriceMirrorSynchronizer
|
|
{
|
|
public function __construct(
|
|
private readonly ProductPurchasePriceRepository $upstream,
|
|
private readonly ProductPurchasePriceMirrorRepository $mirror,
|
|
) {
|
|
}
|
|
|
|
/** @return array{status:string,mode:string,batches:int,rows:int} */
|
|
public function sync(bool $full = false, ?int $batchSize = null, ?int $maxBatches = null): array
|
|
{
|
|
if (! $this->mirror->available()) {
|
|
throw new RuntimeException('product_purchase_price_mirror_schema_missing');
|
|
}
|
|
|
|
$state = $this->mirror->state();
|
|
if (! $full && (
|
|
($state['initial_completed_at'] ?? null) === null
|
|
|| (int) ($state['projection_version'] ?? 1) < ProductPurchasePriceMirrorRepository::CURRENT_PROJECTION_VERSION
|
|
)) {
|
|
return ['status' => 'requires_full_sync', 'mode' => 'incremental', 'batches' => 0, 'rows' => 0];
|
|
}
|
|
|
|
$mode = $full ? 'full' : 'incremental';
|
|
$resume = is_array($state)
|
|
&& ($state['run_mode'] ?? null) === $mode
|
|
&& in_array($state['status'] ?? null, ['running', 'failed'], true)
|
|
&& (int) ($state['projection_version'] ?? 1) >= ProductPurchasePriceMirrorRepository::CURRENT_PROJECTION_VERSION
|
|
&& ($state['window_started_at'] ?? null) !== null
|
|
&& ($state['window_ends_at'] ?? null) !== null;
|
|
$generation = $resume && is_string($state['generation'] ?? null)
|
|
? (string) $state['generation']
|
|
: (string) Str::uuid();
|
|
|
|
if ($resume) {
|
|
$since = Carbon::parse((string) $state['window_started_at']);
|
|
$until = Carbon::parse((string) $state['window_ends_at']);
|
|
} elseif ($full) {
|
|
$since = Carbon::create(1900, 1, 1, 0, 0, 0, config('app.timezone'));
|
|
$until = now()->addSecond();
|
|
} else {
|
|
$overlap = max((int) config('upstream_mirrors.product_purchase_prices.overlap_hours', 48), 1);
|
|
$since = Carbon::parse((string) $state['last_succeeded_at'])->subHours($overlap);
|
|
$until = now()->addSecond();
|
|
}
|
|
|
|
$state = $this->mirror->startRun($mode, $generation, $since, $until, $resume);
|
|
$cursor = ($state['cursor_updated_at'] ?? null) === null
|
|
? null
|
|
: [
|
|
'updated_at' => (string) $state['cursor_updated_at'],
|
|
'item_id' => (int) ($state['cursor_item_id'] ?? 0),
|
|
];
|
|
$batchSize = min(max(
|
|
$batchSize ?? (int) config('upstream_mirrors.product_purchase_prices.batch_size', 1000),
|
|
1,
|
|
), 5000);
|
|
$maxBatches = max(
|
|
$maxBatches ?? (int) config('upstream_mirrors.product_purchase_prices.max_batches_per_run', 5),
|
|
0,
|
|
);
|
|
$batches = 0;
|
|
$rows = (int) ($state['last_upserted_rows'] ?? 0);
|
|
|
|
try {
|
|
while (true) {
|
|
$batch = $this->upstream->mirrorBatch(
|
|
$cursor,
|
|
$since->format('Y-m-d\TH:i:s.v'),
|
|
$until->format('Y-m-d\TH:i:s.v'),
|
|
$batchSize,
|
|
);
|
|
|
|
if ($batch === []) {
|
|
$this->mirror->complete($mode, $generation, $until, $rows);
|
|
|
|
return ['status' => 'succeeded', 'mode' => $mode, 'batches' => $batches, 'rows' => $rows];
|
|
}
|
|
|
|
$last = $batch[array_key_last($batch)];
|
|
$cursor = [
|
|
'updated_at' => (string) $last['purchase_updated_at'],
|
|
'item_id' => (int) $last['item_id'],
|
|
];
|
|
$rows += count($batch);
|
|
$batches++;
|
|
$this->mirror->storeBatch($batch, $cursor, $generation, $rows);
|
|
|
|
if (count($batch) < $batchSize) {
|
|
$this->mirror->complete($mode, $generation, $until, $rows);
|
|
|
|
return ['status' => 'succeeded', 'mode' => $mode, 'batches' => $batches, 'rows' => $rows];
|
|
}
|
|
|
|
if ($maxBatches > 0 && $batches >= $maxBatches) {
|
|
return ['status' => 'running', 'mode' => $mode, 'batches' => $batches, 'rows' => $rows];
|
|
}
|
|
}
|
|
} catch (Throwable $exception) {
|
|
$this->mirror->fail($exception::class);
|
|
throw $exception;
|
|
}
|
|
}
|
|
}
|