'integer', 'effective_date' => 'date', 'end_date' => 'date', 'voided_at' => 'datetime', 'billing_structure' => 'array', 'billing_rows' => 'array', ]; /** 短期(临时)变价:有结束日期,到期后回到原来的长期报价。 */ public function isTemporary(): bool { return $this->end_date !== null && !$this->change?->isStockQuote(); } /** 某一天是否在这一版的执行期间内(开始 ≤ 日期 ≤ 结束;长期版本没有结束)。 */ public function coversDate(\DateTimeInterface $date): bool { return $this->activePeriod($date) !== null; } public function executionPeriods(): array { return (!$this->change?->isStockQuote() ? data_get($this->change?->quote_payload, 'execution_periods') : null) ?: [['effective_date' => $this->effective_date?->toDateString(), 'end_date' => $this->end_date?->toDateString()]]; } public function activePeriod(\DateTimeInterface $date): ?array { $day = $date->format('Y-m-d'); foreach ($this->executionPeriods() as $period) { if (filled($period['effective_date']) && $period['effective_date'] <= $day && (empty($period['end_date']) || $period['end_date'] >= $day)) return $period; } return null; } public function nextPeriod(\DateTimeInterface $date): ?array { foreach ($this->executionPeriods() as $period) { if ($period['effective_date'] > $date->format('Y-m-d')) return $period; } return null; } /** 执行期间文字,如「2026-10-12 至 2026-10-15(临时)」或「2026-10-01 起」。 */ public function periodLabel(): string { if ($this->effective_date === null) { return '-'; } return \App\Support\CourierQuotePeriods::label($this->executionPeriods(), $this->isTemporary()); } /** 是否已有可用于运费核算的结构化计费规则。 */ public function hasBillingRules(): bool { $mode = $this->billing_structure['mode'] ?? null; return is_string($mode) && $mode !== 'other' && is_array($this->billing_rows) && $this->billing_rows !== []; } public function isVoided(): bool { return $this->status === self::STATUS_VOIDED; } /** 存量报价审核后按执行日期生效,不以补签或历史签署资料为前置条件。 */ public function isApprovedStockQuote(): bool { return $this->status === self::STATUS_APPROVED && $this->change?->status === CourierQuoteChange::STATUS_APPROVED && $this->change->isStockQuote(); } public function signatureLabel(): string { return $this->isApprovedStockQuote() && !in_array($this->signature_status, ['历史签署资料已归档', '已签署并归档'], true) ? '存量报价,审核生效(无需签章)' : (string) $this->signature_status; } public function change(): BelongsTo { return $this->belongsTo(CourierQuoteChange::class, 'source_change_id'); } public function signature(): HasOne { return $this->hasOne(CourierQuoteSignature::class, 'quote_version_id'); } }