1d5ea6d261
Implements 2 of the biggest items from /tmp/service/new docs: == Calendar Vizual v2 (from 02-prototip-calendar-vizual.html) == Replaces the FullCalendar week view (the one that visually collapsed after Livewire re-renders) with a server-rendered matrix that the harness already drives through Livewire — no third-party JS to clash with Filament. Layout: 8-column CSS grid (1 row-label + 7 days). Rows are either Posts (Pod 1, Pod 2…) or active masters depending on toolbar switch. Each cell holds 0..N event cards. Per-cell load badge (top-right): hours_planned / capacity → badge color (gray <50%, orange 50–90%, red ≥90%) Drag-drop: HTML5 native, Alpine.js holds the dragEventId, moveEvent($id, $toRowId, $toDate) in PHP updates either post_id or master_id (depending on groupBy mode) plus date — works seamlessly when re-grouping. KPI bar (4 cards above toolbar): - Ore programate X / Y · % capacity - Fișe deschise (orange) - Confirmate X/Y (green) + confirmation rate - No-show alert (red) — scheduled events <24h away that are still unconfirmed Toolbar: - ◀ Week ▶ + Astăzi (reset) - Date label "01 — 07 iunie 2026" - Grupare switch: Pod ↔ Mecanic - Filtru: master dropdown + status dropdown (Confirmate/Neconfirmate/În lucru) Today column highlighted blue; Sunday column hatched as closed (non-interactive, no drop target); Saturday muted as weekend. Event card color = master.color (deterministic, matches profile setting), shown as left border + background tint. Title = client name; meta = "VW Passat · CIU 001"; time = "08:00–12:00 · V.". Click empty cell → quick-create panel (right slide-in) with date+pod pre-filled. Click event → detail panel with Client/Phone/Auto/Plate/ Master/Pod + delete + edit. Legend section at bottom (mecanici dots, load colors, day states). == Hidden Markup (from gap-analysis.md #3) == Adds `hidden_markup_pct` decimal to parts. Customer documents continue to show the standard sell_price; the hidden markup is an internal margin indicator used for B2B contracts and corporate analytics. Part::internalCostWithHiddenMarkup() returns buy_price * (1 + pct/100). Falls back to buy_price when pct is null. Decimal:2 cast so persistence round-trips cleanly. == Schema migration == Idempotent (hasColumn guards): - posts.hours_per_day decimal(5,1) default 10 - posts.description varchar(255) nullable - parts.hidden_markup_pct decimal(5,2) nullable == Tests == +11 new in CalendarBoardV2Test (8) + HiddenMarkupTest (3): - get_days returns 7 days with today flagged + Sunday closed + Saturday weekend - get_rows returns posts when grouped by post + with capacity - get_rows returns masters when grouped by master + Fără maistru fallback row - matrix places events in correct cells + sums hours - move_event reassigns post_id and date - create_appt inserts appointment via panel form - stats compute utilization from events (8h / 60h capacity = 13%) - status filter narrows to confirmed only - hidden_markup applies pct correctly + falls back to buy_price + persists Suite: 196 passed (551 assertions). Was 185. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
57 lines
1.8 KiB
PHP
57 lines
1.8 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Central\Company;
|
|
use App\Models\Central\Plan;
|
|
use App\Models\Tenant\Part;
|
|
use App\Tenancy\TenantManager;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class HiddenMarkupTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$plan = Plan::firstOrCreate(['slug' => 'test'], ['name' => 'T', 'price' => 0, 'features' => []]);
|
|
$company = Company::create(['plan_id' => $plan->id, 'slug' => 'hm-' . uniqid(), 'name' => 'HM Co', 'status' => 'active']);
|
|
app(TenantManager::class)->setCurrent($company);
|
|
}
|
|
|
|
public function test_internal_cost_applies_hidden_markup_percentage(): void
|
|
{
|
|
$part = Part::create([
|
|
'name' => 'Filtru ulei', 'article' => 'F-001',
|
|
'buy_price' => 61.00, 'sell_price' => 85.00,
|
|
'hidden_markup_pct' => 39.34,
|
|
]);
|
|
|
|
// 61 * (1 + 39.34/100) = 61 * 1.3934 = 85.00 (matches sell_price → margin invisible to customer)
|
|
$this->assertEquals(85.00, $part->internalCostWithHiddenMarkup());
|
|
}
|
|
|
|
public function test_internal_cost_falls_back_to_buy_price_when_no_hidden_markup(): void
|
|
{
|
|
$part = Part::create([
|
|
'name' => 'X', 'article' => 'X-1',
|
|
'buy_price' => 100.00, 'sell_price' => 150.00,
|
|
]);
|
|
|
|
$this->assertEquals(100.00, $part->internalCostWithHiddenMarkup());
|
|
}
|
|
|
|
public function test_hidden_markup_persists_through_save_reload(): void
|
|
{
|
|
$part = Part::create([
|
|
'name' => 'Y', 'article' => 'Y-1',
|
|
'buy_price' => 50, 'sell_price' => 80,
|
|
'hidden_markup_pct' => 25.50,
|
|
]);
|
|
$fresh = Part::find($part->id);
|
|
$this->assertEquals(25.50, (float) $fresh->hidden_markup_pct);
|
|
}
|
|
}
|