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>
This commit is contained in:
@@ -23,7 +23,7 @@ class Labor extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'company_id', 'category', 'name_ro', 'name_ru', 'name_en', 'code',
|
||||
'hours', 'pricing_mode', 'fixed_price', 'price', 'is_active', 'notes',
|
||||
'hours', 'pricing_mode', 'fixed_price', 'price', 'cost_price', 'is_active', 'notes',
|
||||
];
|
||||
|
||||
/** Localised name — prefers name_{locale}, falls back to name_ro. */
|
||||
@@ -38,6 +38,7 @@ class Labor extends Model
|
||||
'hours' => 'decimal:2',
|
||||
'fixed_price' => 'decimal:2',
|
||||
'price' => 'decimal:2',
|
||||
'cost_price' => 'decimal:2',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class WorkOrderWork extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'company_id', 'work_order_id', 'labor_id', 'master_id',
|
||||
'name', 'hours', 'price_per_hour', 'total', 'status', 'notes',
|
||||
'name', 'hours', 'price_per_hour', 'discount_pct', 'cost_price', 'total', 'status', 'notes',
|
||||
'requires_approval', 'approved_at', 'approval_token', 'declined_at',
|
||||
'mechanic_status', 'mechanic_started_at', 'mechanic_done_at',
|
||||
'actual_hours', 'paused_seconds_total', 'paused_at',
|
||||
@@ -52,6 +52,8 @@ class WorkOrderWork extends Model
|
||||
protected $casts = [
|
||||
'hours' => 'decimal:2',
|
||||
'price_per_hour' => 'decimal:2',
|
||||
'discount_pct' => 'decimal:2',
|
||||
'cost_price' => 'decimal:2',
|
||||
'total' => 'decimal:2',
|
||||
'requires_approval' => 'boolean',
|
||||
'approved_at' => 'datetime',
|
||||
@@ -173,7 +175,9 @@ class WorkOrderWork extends Model
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::saving(function (self $row) {
|
||||
$row->total = round((float) $row->hours * (float) $row->price_per_hour, 2);
|
||||
$gross = round((float) $row->hours * (float) $row->price_per_hour, 2);
|
||||
$disc = min(100, max(0, (float) $row->discount_pct));
|
||||
$row->total = round($gross * (1 - $disc / 100), 2);
|
||||
if ($row->requires_approval && empty($row->approval_token)) {
|
||||
$row->approval_token = \Illuminate\Support\Str::random(24);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user