Files
portal/app/app/Models/LeadCharge.php
T
Дмитрий e5ee9dce0d fix(db): Plan 4 Task 1 code-review fixes (4 Important issues)
- 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>
2026-05-11 08:51:18 +03:00

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();
}
}