Files
autocrm/routes/web.php
T
Vasyka 06696727dd Faza 6: Activity log + Kanban + Payroll + cleanup
══════ Activity log (Spatie) ══════
- spatie/laravel-activitylog v5 instalat
- Migration cu company_id pentru tenant scoping
- Trait Auditable (App\Models\Concerns\Auditable):
  - LogOptions cu logFillable + logOnlyDirty + dontSubmitEmptyLogs
  - tapActivity auto-fill company_id + causer
  - Descrieri RO (creat/modificat/șters/restaurat)
- Aplicat pe: Client, Vehicle, Lead, Deal, WorkOrder, Payment, Expense
- ActivityResource (group Admin → Jurnal activitate)
  - Listă read-only, scope pe tenant, filtre by description/today

══════ Kanban Work Orders ══════
- Custom Filament page la /app/kanban (group Service)
- 6 coloane (new → diagnosis → agreement → in_work → awaiting_parts → ready)
- Drag-drop nativ HTML5 cu wire:click moveCard()
- Cards arată: număr fișă, client, auto, plate, master, total
- Link 'Deschide' direct la editare WO

══════ Payroll (Salarii) ══════
Schema:
- employee_profiles: user_id, position, base_salary, works_pct, parts_pct
- payroll_runs: period (YYYY-MM), base, works_revenue/pct, parts_margin/pct,
  bonus, fines, advance, total auto-calculat
- payroll_adjustments: bonus/fine/advance per period

PayrollCalculator service:
- compute($userId, $period) — calculează auto:
  - Manopere finalizate de mecanic în luna respectivă (sum total)
  - Marja pieselor montate de el (sell-buy * qty)
  - Bonus + fines + advance from adjustments
  - Total = base + works% + parts% + bonus - fines - advance

Resources Filament (group Finanțe):
- EmployeeProfileResource: profil cu % comisioane
- PayrollRunResource: salarii cu action 'Calculează luna curentă' (toți userii)
  + per-row 'Recalculează'; Sum summary pe total
- PayrollAdjustmentResource: gestionare bonus/penalizări/avansuri

══════ Cleanup ══════
- Șterse toate /__debug, /__seed, /__try-login, /__force-login, /__whoami,
  /__coolify-check (security)
- Routes/web.php conține doar / redirect, /manifest.json, /sw.js

Total Filament tenant routes: 92.
2026-05-07 09:52:01 +00:00

68 lines
2.7 KiB
PHP

<?php
use App\Tenancy\TenantManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
// On a tenant subdomain → redirect to the tenant panel.
if (app(TenantManager::class)->isResolved()) {
return redirect('/app');
}
// On the central domain → redirect to admin.
return redirect('/admin');
});
// PWA — manifest dinamic per tenant.
Route::get('/manifest.json', function (Request $request) {
$tenant = app(TenantManager::class)->current();
$name = $tenant?->display_name ?? $tenant?->name ?? 'AutoCRM';
$themeColor = $tenant?->settings['theme_color'] ?? '#3B82F6';
$shortName = $tenant?->slug ?? 'autocrm';
return response()->json([
'name' => $name,
'short_name' => mb_substr($shortName, 0, 12),
'description' => 'CRM autoservice — ' . $name,
'start_url' => '/app',
'display' => 'standalone',
'orientation' => 'any',
'background_color' => '#ffffff',
'theme_color' => $themeColor,
'lang' => $tenant?->settings['language'] ?? 'ro',
'icons' => [
['src' => '/pwa/icon-192.png', 'sizes' => '192x192', 'type' => 'image/png'],
['src' => '/pwa/icon-512.png', 'sizes' => '512x512', 'type' => 'image/png'],
['src' => '/pwa/icon-maskable.png', 'sizes' => '512x512', 'type' => 'image/png', 'purpose' => 'maskable'],
],
])->header('Cache-Control', 'public, max-age=3600');
});
// Service worker stub — minimal cache for shell.
Route::get('/sw.js', function () {
return response(<<<'JS'
const CACHE = 'autocrm-shell-v1';
const SHELL = ['/manifest.json'];
self.addEventListener('install', e => {
e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL)));
});
self.addEventListener('activate', e => {
e.waitUntil(caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
));
});
self.addEventListener('fetch', e => {
const u = new URL(e.request.url);
if (e.request.method !== 'GET') return;
// network-first for app routes; cache-first for static
if (u.pathname.startsWith('/build/') || u.pathname.startsWith('/pwa/')) {
e.respondWith(caches.match(e.request).then(m => m || fetch(e.request).then(r => {
const copy = r.clone();
caches.open(CACHE).then(c => c.put(e.request, copy));
return r;
})));
}
});
JS, 200, ['Content-Type' => 'application/javascript', 'Cache-Control' => 'public, max-age=3600']);
});