Stage 4 — Labor Catalog: fixed price + default parts + service templates
Schema: - labors.pricing_mode (hourly/fixed) + fixed_price - labor_parts (default parts auto-added with a labor) - service_templates + service_template_items (labor/part bundles) ServiceComposer: - addLabor(wo, labor, withParts) — hourly (hours×rate) or fixed (fixed_price), then auto-adds the labor's default parts - addPart(wo, part, qty) — catalog price snapshot - applyTemplate(wo, template) — adds all labor+part lines, recalcs total - hourlyRate from settings.labor_rate Filament: - LaborResource: pricing_mode (live) toggles hours/fixed_price fields, DefaultPartsRelationManager - ServiceTemplateResource (Service group) with ItemsRelationManager - WorkOrder edit "Aplică șablon" action → applyTemplate - WorksRelationManager CreateAction auto-adds labor default parts Tests (6 new): - hourly rate×hours; fixed uses fixed_price; default parts auto-added; withParts=false skips; applyTemplate adds lines + recalcs total; templates tenant-isolated Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<?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('labors', function (Blueprint $t) {
|
||||
$t->string('pricing_mode', 12)->default('hourly')->after('hours'); // hourly / fixed
|
||||
$t->decimal('fixed_price', 10, 2)->default(0)->after('pricing_mode');
|
||||
});
|
||||
|
||||
Schema::create('labor_parts', function (Blueprint $t) {
|
||||
$t->id();
|
||||
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
|
||||
$t->foreignId('labor_id')->constrained()->cascadeOnDelete();
|
||||
$t->foreignId('part_id')->constrained()->cascadeOnDelete();
|
||||
$t->decimal('qty', 8, 2)->default(1);
|
||||
$t->string('unit', 16)->default('buc');
|
||||
$t->timestamps();
|
||||
|
||||
$t->index(['company_id', 'labor_id']);
|
||||
});
|
||||
|
||||
Schema::create('service_templates', function (Blueprint $t) {
|
||||
$t->id();
|
||||
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
|
||||
$t->string('name');
|
||||
$t->string('category')->nullable();
|
||||
$t->text('notes')->nullable();
|
||||
$t->boolean('is_active')->default(true);
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->index(['company_id', 'is_active']);
|
||||
});
|
||||
|
||||
Schema::create('service_template_items', function (Blueprint $t) {
|
||||
$t->id();
|
||||
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
|
||||
$t->foreignId('service_template_id')->constrained()->cascadeOnDelete();
|
||||
$t->string('kind', 8); // labor / part
|
||||
$t->foreignId('labor_id')->nullable()->constrained()->nullOnDelete();
|
||||
$t->foreignId('part_id')->nullable()->constrained()->nullOnDelete();
|
||||
$t->string('name'); // snapshot label
|
||||
$t->decimal('qty', 8, 2)->default(1); // for parts (and labor hours fallback)
|
||||
$t->decimal('hours', 5, 2)->nullable();// for labor
|
||||
$t->timestamps();
|
||||
|
||||
$t->index(['company_id', 'service_template_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('service_template_items');
|
||||
Schema::dropIfExists('service_templates');
|
||||
Schema::dropIfExists('labor_parts');
|
||||
Schema::table('labors', function (Blueprint $t) {
|
||||
$t->dropColumn(['pricing_mode', 'fixed_price']);
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user