Faza 3.2: Service modules — Norme-ore, Tehnicieni, Fișe lucru
Schema:
- users + specialization, color, hourly_rate (pentru maistri)
- labors: catalog manopere standard cu category/ore/preț (RO+RU)
- work_orders: nr unique per tenant, status workflow (9 stări),
pay_status (3 stări), client/vehicle/master/deal/appointment refs,
complaint/diagnosis/recommendations, total auto-calculat
- wo_works: manopere per fișă, recalc auto la save/delete
- wo_parts: piese per fișă (free-text deocamdată), discount/total auto
Filament resources (group Service):
- LaborResource: CRUD + grupare pe categorie + filter active
- WorkOrderResource: form complex în 4 secțiuni (antet, diagnostic, plată)
+ 2 RelationManagers (Works, Parts)
- MasterResource: vedere User filtrată role=mechanic, edit specializare/
culoare calendar/tarif oră
Conversie auto: la adaugare manoperă din catalog Labor,
form populează numele + ore + preț/oră derivat (price/hours).
Number generator pentru WO: format WO-{YY}-{NNNN} per tenant per an,
calculat în CreateWorkOrder via WorkOrder::generateNumber().
Seed extins:
- 3 mecanici (Vasile/Andrei/Nicolae) cu culori + specializări
- 10 manopere standard din prototipul AutoCRM.html
- 1 fișă demo (BMW X5 plăcuțe Brembo) cu 1 manoperă + 1 piesă, total auto
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?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::table('users', function (Blueprint $t) {
|
||||
$t->string('specialization')->nullable()->after('locale');
|
||||
$t->string('color', 16)->nullable()->after('specialization');
|
||||
$t->decimal('hourly_rate', 8, 2)->nullable()->after('color');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $t) {
|
||||
$t->dropColumn(['specialization', 'color', 'hourly_rate']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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('labors', function (Blueprint $t) {
|
||||
$t->id();
|
||||
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
|
||||
|
||||
$t->string('category'); // Motor / Frâne / Suspensie / ...
|
||||
$t->string('name_ro'); // numele manoperei (ro)
|
||||
$t->string('name_ru')->nullable();
|
||||
$t->string('code', 32)->nullable(); // cod intern opțional
|
||||
|
||||
$t->decimal('hours', 5, 2)->default(1); // norma-oră
|
||||
$t->decimal('price', 10, 2)->default(0); // preț calculat (hours * tarif companie de obicei)
|
||||
$t->boolean('is_active')->default(true);
|
||||
$t->text('notes')->nullable();
|
||||
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->index(['company_id', 'category']);
|
||||
$t->index(['company_id', 'is_active']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('labors');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
<?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('work_orders', function (Blueprint $t) {
|
||||
$t->id();
|
||||
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
|
||||
$t->string('number', 32); // WO-001 — generat per tenant
|
||||
$t->foreignId('client_id')->nullable()->constrained()->nullOnDelete();
|
||||
$t->foreignId('vehicle_id')->nullable()->constrained()->nullOnDelete();
|
||||
$t->foreignId('master_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$t->foreignId('deal_id')->nullable()->constrained()->nullOnDelete();
|
||||
$t->foreignId('appointment_id')->nullable()->constrained()->nullOnDelete();
|
||||
|
||||
$t->date('opened_at');
|
||||
$t->date('closed_at')->nullable();
|
||||
$t->unsignedInteger('mileage_in')->nullable();
|
||||
$t->unsignedInteger('mileage_out')->nullable();
|
||||
|
||||
$t->text('complaint')->nullable(); // jaluire client
|
||||
$t->text('diagnosis')->nullable();
|
||||
$t->text('recommendations')->nullable();
|
||||
|
||||
$t->string('status')->default('new');
|
||||
// new / diagnosis / agreement / approved / in_work /
|
||||
// awaiting_parts / ready / done / cancelled
|
||||
$t->string('pay_status')->default('unpaid'); // unpaid / partial / paid
|
||||
$t->boolean('approved')->default(false);
|
||||
$t->timestamp('approved_at')->nullable();
|
||||
|
||||
$t->decimal('discount_pct', 5, 2)->default(0);
|
||||
$t->decimal('total', 12, 2)->default(0); // calculat (works + parts - discount)
|
||||
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->unique(['company_id', 'number']);
|
||||
$t->index(['company_id', 'status']);
|
||||
$t->index(['company_id', 'opened_at']);
|
||||
});
|
||||
|
||||
Schema::create('wo_works', function (Blueprint $t) {
|
||||
$t->id();
|
||||
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
|
||||
$t->foreignId('work_order_id')->constrained()->cascadeOnDelete();
|
||||
$t->foreignId('labor_id')->nullable()->constrained()->nullOnDelete();
|
||||
$t->foreignId('master_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
|
||||
$t->string('name'); // snapshot din labor.name_ro la momentul adăugării
|
||||
$t->decimal('hours', 5, 2)->default(1);
|
||||
$t->decimal('price_per_hour', 10, 2)->default(0); // tarif normo-oră
|
||||
$t->decimal('total', 10, 2)->default(0); // hours * price_per_hour
|
||||
$t->string('status')->default('todo'); // todo / in_progress / done
|
||||
$t->text('notes')->nullable();
|
||||
|
||||
$t->timestamps();
|
||||
|
||||
$t->index(['company_id', 'work_order_id']);
|
||||
});
|
||||
|
||||
Schema::create('wo_parts', function (Blueprint $t) {
|
||||
$t->id();
|
||||
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
|
||||
$t->foreignId('work_order_id')->constrained()->cascadeOnDelete();
|
||||
|
||||
$t->string('name'); // ex: "Filtru ulei MANN W811/80"
|
||||
$t->string('article', 64)->nullable();
|
||||
$t->string('brand', 64)->nullable();
|
||||
$t->decimal('qty', 8, 2)->default(1);
|
||||
$t->string('unit', 16)->default('buc');
|
||||
$t->decimal('buy_price', 10, 2)->default(0);
|
||||
$t->decimal('sell_price', 10, 2)->default(0);
|
||||
$t->decimal('discount_pct', 5, 2)->default(0);
|
||||
$t->decimal('total', 12, 2)->default(0); // qty * sell_price * (1-disc/100)
|
||||
$t->string('status')->default('needed'); // needed / ordered / delivered / installed
|
||||
$t->text('notes')->nullable();
|
||||
|
||||
$t->timestamps();
|
||||
|
||||
$t->index(['company_id', 'work_order_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('wo_parts');
|
||||
Schema::dropIfExists('wo_works');
|
||||
Schema::dropIfExists('work_orders');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user