Files
autocrm/app/Models/Tenant/Labor.php
T
Vasyka ce9f082612 feat: per-row and global discount with margin validation
Parts (wo_parts) already had discount_pct; now also added to wo_works.
Labor catalog gets a cost_price field as the hard minimum sell floor.

- WorkOrderWork: discount_pct + cost_price fields; total calculation
  now includes discount: hours × price_per_hour × (1 − disc/100)
- Labor model: cost_price field (used to pre-fill work rows and validate)
- WorksRelationManager: discount_pct field with live helper text showing
  effective price + margin %; validation blocks save if below buy cost
  or below company min_discount_margin_pct; "Reducere globală" header
  action applies % to all work rows (skips rows that would breach limits)
- PartsRelationManager: same validation on existing discount_pct field;
  helper text (🟢/🟠/🔴) shows effective price + margin in real time;
  "Reducere globală" header action for all parts
- Settings page: new "Reduceri & limite preț" section with
  min_discount_margin_pct — 0 = no limit, >0 = hard block below that %
- LaborResource form: cost_price field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 04:48:15 +00:00

59 lines
1.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Models\Tenant;
use App\Models\Concerns\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Labor extends Model
{
use BelongsToTenant, SoftDeletes;
public const CATEGORIES = [
'Motor', 'Frâne', 'Suspensie', 'Anvelope', 'ITP', 'Cutie viteze',
'Caroserie', 'Electrică', 'Climatizare', 'Eșapament', 'Altele',
];
public const PRICING_MODES = [
'hourly' => 'Pe oră (normă × tarif)',
'fixed' => 'Preț fix',
];
protected $fillable = [
'company_id', 'category', 'name_ro', 'name_ru', 'name_en', 'code',
'hours', 'pricing_mode', 'fixed_price', 'price', 'cost_price', 'is_active', 'notes',
];
/** Localised name — prefers name_{locale}, falls back to name_ro. */
public function label(?string $locale = null): string
{
$locale = $locale ?: app()->getLocale();
$col = 'name_' . $locale;
return $this->{$col} ?: ($this->name_ro ?: '');
}
protected $casts = [
'hours' => 'decimal:2',
'fixed_price' => 'decimal:2',
'price' => 'decimal:2',
'cost_price' => 'decimal:2',
'is_active' => 'boolean',
];
public function laborParts(): HasMany
{
return $this->hasMany(LaborPart::class);
}
/** Effective line total for this labor given the tenant hourly rate. */
public function effectiveTotal(float $hourlyRate): float
{
if ($this->pricing_mode === 'fixed') {
return (float) $this->fixed_price;
}
return round((float) $this->hours * $hourlyRate, 2);
}
}