fix(pdf): fix storage volume permissions blocking PDF generation

Named volume mounted root-owned over the app user's chown, so every
PDF (orçamento, fatura, download) failed with 500. Entrypoint now
fixes ownership on container start and drops to the app user.

feat(dashboard): decouple pipeline state from period filter

Status counts and recent OTs no longer disappear when an open order
was created before the selected period window.

feat(workorders): add vehicle/total columns, deadline urgency colors,
on-time delivery saldo and efficiency metric, plate search

Expose completed_at from wo_status_log so the frontend can compare
actual completion date against the deadline. Add "Eficiência de
Entrega" metric to Dashboard and Reports.

feat(reports): add dedicated vehicle service history report

New page with per-vehicle summary (OT count, total spent, last visit)
and full itemized service history with print output.

chore: relabel OT status "Concluir OT" -> "Concluída" and
"Iniciar Trabalho" -> "Em Progresso" (action buttons unchanged)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Luciano Milani
2026-07-09 19:11:02 +01:00
parent 4994abf4e2
commit 76b50fec84
12 changed files with 614 additions and 51 deletions
+13 -4
View File
@@ -23,8 +23,15 @@ type WorkOrder struct {
CreatedBy *string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CompletedAt *time.Time `json:"completed_at"`
}
const completedAtSubquery = `(
SELECT changed_at FROM wo_status_log
WHERE work_order_id = work_orders.id AND to_status = 'completed'
ORDER BY changed_at DESC LIMIT 1
)`
type WOItem struct {
ID string `json:"id"`
WorkOrderID string `json:"work_order_id"`
@@ -76,7 +83,8 @@ func ValidateTransition(from, to string) error {
func ListWorkOrders(ctx context.Context, conn *pgxpool.Conn, status string) ([]*WorkOrder, error) {
q := `SELECT id, number, client_id, vehicle_id, status,
COALESCE(internal_notes,''), COALESCE(client_notes,''), COALESCE(eta_days,1), real_deadline,
COALESCE(payment_method,''), payment_date, created_by, created_at, updated_at
COALESCE(payment_method,''), payment_date, created_by, created_at, updated_at,
` + completedAtSubquery + `
FROM work_orders`
args := []any{}
if status != "" {
@@ -94,7 +102,7 @@ func ListWorkOrders(ctx context.Context, conn *pgxpool.Conn, status string) ([]*
var wo WorkOrder
if err := rows.Scan(&wo.ID, &wo.Number, &wo.ClientID, &wo.VehicleID,
&wo.Status, &wo.InternalNotes, &wo.ClientNotes, &wo.ETADays, &wo.RealDeadline,
&wo.PaymentMethod, &wo.PaymentDate, &wo.CreatedBy, &wo.CreatedAt, &wo.UpdatedAt); err != nil {
&wo.PaymentMethod, &wo.PaymentDate, &wo.CreatedBy, &wo.CreatedAt, &wo.UpdatedAt, &wo.CompletedAt); err != nil {
return nil, err
}
list = append(list, &wo)
@@ -180,11 +188,12 @@ func GetWorkOrderDetail(ctx context.Context, conn *pgxpool.Conn, id string) (*Wo
err := conn.QueryRow(ctx, `
SELECT id, number, client_id, vehicle_id, status,
COALESCE(internal_notes,''), COALESCE(client_notes,''), COALESCE(eta_days,1), real_deadline,
COALESCE(payment_method,''), payment_date, created_by, created_at, updated_at
COALESCE(payment_method,''), payment_date, created_by, created_at, updated_at,
`+completedAtSubquery+`
FROM work_orders WHERE id=$1`, id).
Scan(&wo.ID, &wo.Number, &wo.ClientID, &wo.VehicleID,
&wo.Status, &wo.InternalNotes, &wo.ClientNotes, &wo.ETADays, &wo.RealDeadline,
&wo.PaymentMethod, &wo.PaymentDate, &wo.CreatedBy, &wo.CreatedAt, &wo.UpdatedAt)
&wo.PaymentMethod, &wo.PaymentDate, &wo.CreatedBy, &wo.CreatedAt, &wo.UpdatedAt, &wo.CompletedAt)
if err != nil {
return nil, err
}