Stage 8 — Smart Pricing Engine: contextual coefficients
Contextual multipliers layered on top of base MarkupRule pricing, applied
per work-order line based on vehicle, client and urgency.
Schema:
- pricing_coefficients (multiplier, conditions JSON, priority, stackable)
- vehicles.vehicle_class (sedan/suv/commercial/hybrid/ev/premium)
- clients.is_vip
- work_orders.urgency (normal/urgent/express)
PricingEngine::quote(Part, Vehicle?, Client?, urgency):
- base = MarkupRule on buy_price (fallback sell_price or buy×1.30)
- context: class (explicit or inferred hybrid/ev from fuel), age, vip, urgency
- stackable coefficients all multiply; non-stackable take only the highest
- returns {base, final, applied[]} breakdown
PricingCoefficient::matches(ctx) — classes/age range/vip/urgency conditions
(empty = always applies).
Filament:
- PricingCoefficientResource with condition builder (classes, age, vip, urgency)
- vehicle_class select, client is_vip toggle, WO urgency select
- "Preț inteligent" action on WO parts shows breakdown + applies sell_price
Tests (6 new):
- base-only without coefficients; age coefficient gating; VIP; express urgency;
stackable multiply vs non-stackable highest-wins; hybrid inferred from fuel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('pricing_coefficients', function (Blueprint $t) {
|
||||
$t->id();
|
||||
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
|
||||
$t->string('name');
|
||||
$t->decimal('multiplier', 6, 3)->default(1); // 1.15 = +15%
|
||||
$t->json('conditions')->nullable(); // {classes:[], age_min, age_max, client_vip, urgency:[]}
|
||||
$t->unsignedSmallInteger('priority')->default(100);
|
||||
$t->boolean('stackable')->default(true);
|
||||
$t->boolean('is_active')->default(true);
|
||||
$t->timestamps();
|
||||
|
||||
$t->index(['company_id', 'is_active', 'priority']);
|
||||
});
|
||||
|
||||
Schema::table('vehicles', function (Blueprint $t) {
|
||||
$t->string('vehicle_class', 24)->nullable()->after('fuel');
|
||||
});
|
||||
|
||||
Schema::table('clients', function (Blueprint $t) {
|
||||
$t->boolean('is_vip')->default(false)->after('status');
|
||||
});
|
||||
|
||||
Schema::table('work_orders', function (Blueprint $t) {
|
||||
$t->string('urgency', 16)->default('normal')->after('status'); // normal / urgent / express
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('work_orders', fn (Blueprint $t) => $t->dropColumn('urgency'));
|
||||
Schema::table('clients', fn (Blueprint $t) => $t->dropColumn('is_vip'));
|
||||
Schema::table('vehicles', fn (Blueprint $t) => $t->dropColumn('vehicle_class'));
|
||||
Schema::dropIfExists('pricing_coefficients');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user