CaiWuXiTong/app/Support/CourierQuotePeriodCorrection.php

62 lines
4.1 KiB
PHP

<?php
namespace App\Support;
use App\Models\Business\CourierQuoteChange;
use App\Models\Business\CourierQuoteChangeEvent;
use App\Models\Business\CourierQuoteSignature;
use App\Models\Business\CourierQuoteVersion;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
final class CourierQuotePeriodCorrection
{
public function available(CourierQuoteChange $change): bool
{
if ($change->isStockQuote() || $change->status !== CourierQuoteChange::STATUS_APPROVED
|| !$change->version || $change->version->status !== CourierQuoteVersion::STATUS_APPROVED) return false;
// Once any attempt has entered the external signing flow, its execution terms are immutable.
return !CourierQuoteSignature::withTrashed()->disableCache()->where('quote_version_id', $change->version->id)
->get()->contains(static fn ($signature) => !$signature->canSend()
|| filled($signature->esign_file_id) || $signature->sent_at !== null || filled($signature->signed_pdf_path));
}
public function correct(CourierQuoteChange $change, array $data, int $userId): CourierQuoteChange
{
app(CourierQuoteWorkflowPermissions::class)->assertReviewer($change, $userId);
$data = CourierQuotePeriods::normalize($data);
return DB::transaction(function () use ($change, $data, $userId) {
$change = CourierQuoteChange::query()->disableCache()->lockForUpdate()->findOrFail($change->id);
$version = CourierQuoteVersion::query()->disableCache()->where('source_change_id', $change->id)->lockForUpdate()->firstOrFail();
$signatures = CourierQuoteSignature::withTrashed()->disableCache()->where('quote_version_id', $version->id)->lockForUpdate()->get();
$change->setRelation('version', $version);
if (!$this->available($change)) {
throw ValidationException::withMessages(['periods' => '仅已审核且从未推送签署的变价可更正执行区间;已进入签署流程的原文须保留。']);
}
$old = ['periods' => $change->executionPeriods(), 'documents' => $signatures->map->only([
'id', 'source_pdf_path', 'source_pdf_sha256', 'status',
])->all()];
$retrospective = $data['effective_date'] < today()->toDateString();
$change->update([
'effective_date' => $data['effective_date'], 'end_date' => $data['end_date'],
'quote_payload' => CourierQuotePeriods::payload($change->quote_payload, $data['execution_periods']),
'is_retrospective' => $retrospective,
'recalculation_status' => $retrospective ? CourierQuoteChange::RECALCULATION_BLOCKED : CourierQuoteChange::RECALCULATION_NOT_REQUIRED,
'recalculation_note' => $retrospective ? '执行区间已更正;尚未执行历史运费补差核算,原运费未改动。' : null,
]);
$version->update(['effective_date' => $data['effective_date'], 'end_date' => $data['end_date']]);
// A new private file is generated; the old document remains stored and is referenced by the audit.
$signature = app(CourierQuoteSignatureService::class)->prepareVersion($version, $userId, today()->toDateString());
CourierQuoteChangeEvent::create([
'company_id' => $change->company_id, 'quote_change_id' => $change->id,
'event_type' => 'periods_corrected', 'from_status' => $change->status, 'to_status' => $change->status,
'note' => '审核人更正未推送报价的执行区间:' . $change->periodLabel() . '。区间外沿用原长期报价;价格不变,待签文件已重新生成。',
'details_json' => json_encode(['before' => $old, 'after' => ['periods' => $change->executionPeriods(),
'source_pdf_path' => $signature->source_pdf_path, 'source_pdf_sha256' => $signature->source_pdf_sha256]], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
'created_by' => $userId,
]);
return $change->fresh(['version.signature', 'events']);
});
}
}