e5ee9dce0d
- chk_lead_charges_prepaid_zero_price moved inline в CREATE TABLE lead_charges (consistent с ~30 другими CHECK constraint'ами в schema.sql). - LeadCharge.casts() — убран no-op 'charge_source' => 'string' (Eloquent возвращает VARCHAR как string без cast'а; consistent с SupplierLead.platform). - SchemaDeltaTest — добавлен uses(DatabaseTransactions::class) для tests 1+2 (rollback после теста, project convention LeadChargeTest/PricingTierTest). - SchemaDeltaTest test #5 — замена destructive migrate:fresh на static parse count(CREATE TABLE) / count(CREATE INDEX) / count(CREATE POLICY) в schema.sql. Устраняет cross-test coupling в sequential pest run; параллельно убирает LARAVEL_PARALLEL_TESTING skip — теперь все 5 тестов выполняются в parallel. Метрики из static parse: 62 base tables / 117 indexes / 39 RLS policies (совпадают с schema v8.19, spec §2.4). All 5 SchemaDeltaTest assertions still pass. No new schema changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
84 lines
2.3 KiB
PHP
84 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use Database\Factories\LeadChargeFactory;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
/**
|
|
* Append-only ledger списания за каждый доставленный лид.
|
|
*
|
|
* Tenant-scoped (RLS tenant_isolation, ENABLE+FORCE). FK на партиционированную
|
|
* deals(id, received_at) — composite + DEFERRABLE INITIALLY DEFERRED для
|
|
* атомарного INSERT deal+charge в одной транзакции.
|
|
*
|
|
* Append-only: GRANT ограничен SELECT+INSERT (UPDATE/DELETE недопустимы для
|
|
* целостности audit trail в биллинге).
|
|
*
|
|
* Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §7.4
|
|
*
|
|
* @mixin IdeHelperLeadCharge
|
|
*/
|
|
class LeadCharge extends Model
|
|
{
|
|
/** @use HasFactory<LeadChargeFactory> */
|
|
use HasFactory;
|
|
|
|
public $timestamps = false;
|
|
|
|
protected $table = 'lead_charges';
|
|
|
|
protected $fillable = [
|
|
'tenant_id',
|
|
'deal_id',
|
|
'deal_received_at',
|
|
'tier_no',
|
|
'charge_source',
|
|
'price_per_lead_kopecks',
|
|
'charged_at',
|
|
'created_at',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'tier_no' => 'integer',
|
|
'price_per_lead_kopecks' => 'integer',
|
|
'deal_received_at' => 'datetime',
|
|
'charged_at' => 'datetime',
|
|
'created_at' => 'datetime',
|
|
];
|
|
}
|
|
|
|
/** @return BelongsTo<Tenant, $this> */
|
|
public function tenant(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Tenant::class);
|
|
}
|
|
|
|
/**
|
|
* Связь по deal_id (без deal_received_at — composite PK на партиционированной).
|
|
* Реальная целостность обеспечивается composite FK на уровне БД.
|
|
*
|
|
* @return BelongsTo<Deal, $this>
|
|
*/
|
|
public function deal(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Deal::class, 'deal_id', 'id');
|
|
}
|
|
|
|
public function getPriceRublesAttribute(): float
|
|
{
|
|
return $this->price_per_lead_kopecks / 100;
|
|
}
|
|
|
|
protected static function newFactory(): LeadChargeFactory
|
|
{
|
|
return LeadChargeFactory::new();
|
|
}
|
|
}
|