Files
portal/app/app/Models/PricingTier.php
T
Дмитрий 71e38ee0a9 feat(models): add PricingTier model with kopecks→rubles accessor + current() snapshot
- app/Models/PricingTier.php — fillable + casts (date, boolean, integer)
  + accessor priceRubles (kopecks → float rubles)
  + scopeActive (is_active=true AND effective_from <= today)
  + static current() — keyed by tier_no Collection<int, PricingTier>
- database/factories/PricingTierFactory.php — реалистичные ступени (300/700/1000/.../null)
- tests/Feature/Models/PricingTierTest.php — 4 теста: factory, accessor,
  scopeActive, current() snapshot всех 7 ступеней

ide-helper:models -W -M -N перегенерил docblocks (WebhookDedupKey synced
после schema v8.16).

Pest: 455 / 453 passed / 2 skipped (449 + 4 новых = 453).
Larastan: 0 errors. Pint auto-fix.

Spec: §7.2
Plan: Task 7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:48:00 +03:00

83 lines
2.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Models;
use Database\Factories\PricingTierFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Ступень 7-ступенчатого объёмного тарифа Лидерры (volume billing).
*
* SaaS-level конфигурация (один тариф на всю Лидерру; per-tenant override out of scope MVP).
* Цена хранится в копейках (integer) для избежания floating-point округлений в money-расчётах.
* Месячный сброс счётчика — 1-го числа каждого месяца cron'ом (Plan 4).
*
* Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §7.2
*
* @mixin IdeHelperPricingTier
*/
class PricingTier extends Model
{
/** @use HasFactory<PricingTierFactory> */
use HasFactory;
protected $table = 'pricing_tiers';
protected $fillable = [
'tier_no',
'leads_in_tier',
'price_per_lead_kopecks',
'is_active',
'effective_from',
];
protected function casts(): array
{
return [
'tier_no' => 'integer',
'leads_in_tier' => 'integer',
'price_per_lead_kopecks' => 'integer',
'is_active' => 'boolean',
'effective_from' => 'date',
];
}
public function getPriceRublesAttribute(): float
{
return $this->price_per_lead_kopecks / 100;
}
/**
* @param Builder<PricingTier> $query
* @return Builder<PricingTier>
*/
public function scopeActive(Builder $query): Builder
{
return $query->where('is_active', true)
->where('effective_from', '<=', now()->toDateString());
}
/**
* Возвращает текущий активный набор ступеней, ключевизированный по tier_no (1..7).
*
* @return Collection<int, PricingTier>
*/
public static function current(): Collection
{
return self::active()
->orderBy('tier_no')
->get()
->keyBy('tier_no');
}
protected static function newFactory(): PricingTierFactory
{
return PricingTierFactory::new();
}
}