feat(service-auto): phase 2 — comenzi browse, id_sucursala, cache, migrare SQL
Backend: - GET /api/service-auto/comenzi cu paginare server-side, filtre dată/status - ComandaRequest.id_sucursala (Optional) + FirmaItem.id_mama - get_firme() expune id_mama din V_NOM_FIRME - callproc SP_CREEAZA_COMANDA_PROTOTIP cu 7 argumente (+ p_id_sucursala) - Cache TTL in-process: tip_deviz 24h, masini 5min Frontend: - ComenziBrowseView.vue — DataTable lazy + filtre + status badges - ComandaNoua.vue — company store integration, idSucursala computed - service-auto/stores/sharedStores.js (createCompaniesStore factory) - HamburgerMenu: secțiune Service Auto (Comenzi + Comandă Nouă) - router: /service-auto/comenzi SQL: - migrations/ff_2026_04_12_01_AUTO.sql — idempotent (COLUMNEXIST guard + CREATE OR REPLACE SP) - onboarding_roa_web.sql — versioned, parametrizat cu :SCHEMA_NAME - .claude/rules/oracle-migrations.md — convenție ff_YYYY_MM_DD_NN_MODULE.sql Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
71
.claude/rules/oracle-migrations.md
Normal file
71
.claude/rules/oracle-migrations.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
paths: "**/*.sql,docs/service-auto/migrations/**"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Oracle SQL Migration Script Rules
|
||||||
|
|
||||||
|
You are an Oracle SQL migration script writer. Transform raw DDL/DML into idempotent migration scripts following these rules:
|
||||||
|
|
||||||
|
## STRUCTURE
|
||||||
|
- Header comment: `-- brief description` (e.g. `-- adaugare coloana nom_firme.caen_revizie`)
|
||||||
|
- Body (idempotency rules below)
|
||||||
|
- Footer: `exec pack_migrare.UpdateVersiune('<filename_without_.sql>'); commit;`
|
||||||
|
|
||||||
|
## IDEMPOTENCY RULES
|
||||||
|
|
||||||
|
1. **ALTER TABLE ADD COLUMN** → wrap in:
|
||||||
|
```sql
|
||||||
|
BEGIN
|
||||||
|
IF PACK_MIGRARE.COLUMNEXIST('TABLE','COL')=0 THEN
|
||||||
|
EXECUTE IMMEDIATE '...';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **CREATE OR REPLACE VIEW/PROCEDURE/FUNCTION** → keep as-is (already idempotent)
|
||||||
|
|
||||||
|
3. **INSERT** → replace with:
|
||||||
|
```sql
|
||||||
|
MERGE INTO table USING DUAL ON (key condition)
|
||||||
|
WHEN NOT MATCHED THEN INSERT (cols) VALUES (vals);
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **UPDATE** → keep as-is
|
||||||
|
|
||||||
|
5. **CREATE TABLE** → wrap in:
|
||||||
|
```sql
|
||||||
|
BEGIN
|
||||||
|
IF PACK_MIGRARE.OBJECTEXIST('TABLE','TABLE')=0 THEN
|
||||||
|
EXECUTE IMMEDIATE '...';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **DROP** → wrap in:
|
||||||
|
```sql
|
||||||
|
BEGIN
|
||||||
|
IF PACK_MIGRARE.OBJECTEXIST('OBJ')=1 THEN
|
||||||
|
EXECUTE IMMEDIATE 'DROP...';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **COMMENT ON** → keep as plain DDL (not inside EXECUTE IMMEDIATE)
|
||||||
|
|
||||||
|
8. In MERGE/INSERT: omit NULL-valued columns and CLOB columns entirely
|
||||||
|
|
||||||
|
## FILENAME CONVENTION
|
||||||
|
|
||||||
|
```
|
||||||
|
ff_YYYY_MM_DD_NN_<MODULE>.sql
|
||||||
|
```
|
||||||
|
- `YYYY_MM_DD` — data migrării
|
||||||
|
- `NN` — secvență 2 cifre (01, 02...)
|
||||||
|
- `<MODULE>` — modulul căruia îi aparține migrarea (ex: AUTO, FACTURARE, CONTAB)
|
||||||
|
|
||||||
|
## LANGUAGE
|
||||||
|
Comments: write in Romanian.
|
||||||
|
Output: only the SQL script, no explanation.
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -543,3 +543,4 @@ scripts/ralph/usage.jsonl
|
|||||||
|
|
||||||
# Service-auto reference material (local only, not in repo)
|
# Service-auto reference material (local only, not in repo)
|
||||||
docs/service-auto/*.pdf
|
docs/service-auto/*.pdf
|
||||||
|
docs/service-auto/HANDOFF.md
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import time
|
import time
|
||||||
from typing import List
|
from datetime import date
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
import oracledb
|
import oracledb
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
|
||||||
from shared.auth.dependencies import get_current_user
|
from shared.auth.dependencies import get_current_user
|
||||||
from shared.auth.models import CurrentUser
|
from shared.auth.models import CurrentUser
|
||||||
from shared.database.oracle_pool import oracle_pool
|
from shared.database.oracle_pool import oracle_pool
|
||||||
|
|
||||||
from ..schemas.comanda import (
|
from ..schemas.comanda import (
|
||||||
ComandaRequest, ComandaResponse,
|
ComandaListResponse, ComandaRequest, ComandaResponse,
|
||||||
FirmaItem, TipDevizItem, MasinaClientItem,
|
FirmaItem, TipDevizItem, MasinaClientItem,
|
||||||
)
|
)
|
||||||
from ..services.comanda_service import ComandaService
|
from ..services.comanda_service import ComandaService
|
||||||
@@ -51,6 +52,26 @@ async def get_masini(_: CurrentUser = Depends(get_current_user)):
|
|||||||
return await LookupService.get_masini()
|
return await LookupService.get_masini()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/comenzi", response_model=ComandaListResponse)
|
||||||
|
async def list_comenzi(
|
||||||
|
page: int = Query(default=1, ge=1),
|
||||||
|
per_page: int = Query(default=20, ge=1, le=100),
|
||||||
|
validat: Optional[int] = Query(default=None, ge=0, le=1),
|
||||||
|
data_de_la: Optional[date] = Query(default=None),
|
||||||
|
data_pana_la: Optional[date] = Query(default=None),
|
||||||
|
_: CurrentUser = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
# NOTE: DEV_ORDL has no id_firma column — firmă filter not available at DB level.
|
||||||
|
# All comenzi in MARIUSM_AUTO schema are visible (companies 110/167/169 share schema).
|
||||||
|
return await ComandaService.get_comenzi(
|
||||||
|
page=page,
|
||||||
|
per_page=per_page,
|
||||||
|
validat=validat,
|
||||||
|
data_de_la=data_de_la,
|
||||||
|
data_pana_la=data_pana_la,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/comenzi", response_model=ComandaResponse)
|
@router.post("/comenzi", response_model=ComandaResponse)
|
||||||
async def creeaza_comanda(
|
async def creeaza_comanda(
|
||||||
data: ComandaRequest,
|
data: ComandaRequest,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
@@ -6,6 +8,7 @@ class ComandaRequest(BaseModel):
|
|||||||
id_masiniclient: int
|
id_masiniclient: int
|
||||||
solicitari: str
|
solicitari: str
|
||||||
id_firma: int
|
id_firma: int
|
||||||
|
id_sucursala: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class ComandaResponse(BaseModel):
|
class ComandaResponse(BaseModel):
|
||||||
@@ -18,6 +21,7 @@ class FirmaItem(BaseModel):
|
|||||||
id_firma: int
|
id_firma: int
|
||||||
firma: str
|
firma: str
|
||||||
schema_name: str
|
schema_name: str
|
||||||
|
id_mama: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class TipDevizItem(BaseModel):
|
class TipDevizItem(BaseModel):
|
||||||
@@ -29,3 +33,22 @@ class TipDevizItem(BaseModel):
|
|||||||
class MasinaClientItem(BaseModel):
|
class MasinaClientItem(BaseModel):
|
||||||
id_masiniclient: int
|
id_masiniclient: int
|
||||||
label: str # "PARTENER — MARCA MASINA, NRINMAT (ANFABRICATIE)"
|
label: str # "PARTENER — MARCA MASINA, NRINMAT (ANFABRICATIE)"
|
||||||
|
|
||||||
|
|
||||||
|
class ComandaListItem(BaseModel):
|
||||||
|
id_ordl: int
|
||||||
|
nrord: str
|
||||||
|
datai: Optional[str] # ISO date "YYYY-MM-DD"
|
||||||
|
validat: int # 0=deschisă, 1=validată
|
||||||
|
inchis_fortat: int # 1=arhivată fără validare
|
||||||
|
id_tip: int
|
||||||
|
tip_denumire: str
|
||||||
|
vehicul: str # "PARTENER — MARCA MASINA, NRINMAT (AN)"
|
||||||
|
id_masiniclient: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
|
class ComandaListResponse(BaseModel):
|
||||||
|
comenzi: List[ComandaListItem]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
per_page: int
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import re
|
import re
|
||||||
from typing import NoReturn
|
from datetime import date
|
||||||
|
from typing import List, NoReturn, Optional
|
||||||
|
|
||||||
import oracledb
|
import oracledb
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from shared.database.oracle_pool import oracle_pool
|
from shared.database.oracle_pool import oracle_pool
|
||||||
from ..schemas.comanda import ComandaRequest, ComandaResponse
|
from ..schemas.comanda import (
|
||||||
|
ComandaListItem, ComandaListResponse, ComandaRequest, ComandaResponse,
|
||||||
|
)
|
||||||
from .. import logger
|
from .. import logger
|
||||||
|
|
||||||
|
_MAX_PER_PAGE = 100
|
||||||
|
|
||||||
|
|
||||||
def _handle_oracle_error(e: Exception) -> NoReturn:
|
def _handle_oracle_error(e: Exception) -> NoReturn:
|
||||||
"""
|
"""
|
||||||
@@ -81,6 +86,7 @@ class ComandaService:
|
|||||||
data.id_masiniclient, # p_id_masiniclient IN NUMBER
|
data.id_masiniclient, # p_id_masiniclient IN NUMBER
|
||||||
data.solicitari, # p_solicitari IN VARCHAR2
|
data.solicitari, # p_solicitari IN VARCHAR2
|
||||||
data.id_firma, # p_id_firma IN NUMBER
|
data.id_firma, # p_id_firma IN NUMBER
|
||||||
|
data.id_sucursala, # p_id_sucursala IN NUMBER (None for parent firm)
|
||||||
out_id_ordl, # p_id_ordl OUT NUMBER
|
out_id_ordl, # p_id_ordl OUT NUMBER
|
||||||
out_nrord, # p_nrord OUT VARCHAR2
|
out_nrord, # p_nrord OUT VARCHAR2
|
||||||
],
|
],
|
||||||
@@ -107,3 +113,108 @@ class ComandaService:
|
|||||||
nrord=nrord,
|
nrord=nrord,
|
||||||
mesaj=f"Comanda {nrord} creata cu succes.",
|
mesaj=f"Comanda {nrord} creata cu succes.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_comenzi(
|
||||||
|
page: int,
|
||||||
|
per_page: int,
|
||||||
|
validat: Optional[int],
|
||||||
|
data_de_la: Optional[date],
|
||||||
|
data_pana_la: Optional[date],
|
||||||
|
) -> ComandaListResponse:
|
||||||
|
per_page = min(per_page, _MAX_PER_PAGE)
|
||||||
|
offset = (page - 1) * per_page
|
||||||
|
|
||||||
|
where_parts = ["d.sters = 0"]
|
||||||
|
filter_params: dict = {}
|
||||||
|
|
||||||
|
if validat is not None:
|
||||||
|
where_parts.append("d.validat = :validat")
|
||||||
|
filter_params["validat"] = validat
|
||||||
|
|
||||||
|
if data_de_la is not None:
|
||||||
|
where_parts.append("d.datai >= :data_de_la")
|
||||||
|
filter_params["data_de_la"] = data_de_la
|
||||||
|
|
||||||
|
if data_pana_la is not None:
|
||||||
|
# +1 day range avoids TRUNC (keeps index use on datai)
|
||||||
|
where_parts.append("d.datai < :data_pana_la + 1")
|
||||||
|
filter_params["data_pana_la"] = data_pana_la
|
||||||
|
|
||||||
|
where_clause = " AND ".join(where_parts)
|
||||||
|
|
||||||
|
base_from = f"""
|
||||||
|
FROM MARIUSM_AUTO.DEV_ORDL d
|
||||||
|
LEFT JOIN MARIUSM_AUTO.NOM_LUCRARI l ON d.id_lucrare = l.id_lucrare
|
||||||
|
LEFT JOIN MARIUSM_AUTO.AUTO_VMASINICLIENTI mc
|
||||||
|
ON d.id_masiniclient = mc.id_masiniclient
|
||||||
|
LEFT JOIN MARIUSM_AUTO.DEV_TIP_DEVIZ t ON d.id_tip = t.id_tip
|
||||||
|
WHERE {where_clause}
|
||||||
|
"""
|
||||||
|
|
||||||
|
count_query = f"SELECT COUNT(*) {base_from}"
|
||||||
|
data_query = f"""
|
||||||
|
SELECT d.id_ordl, l.nrord, d.datai, d.validat, d.inchis_fortat,
|
||||||
|
d.id_tip, t.denumire,
|
||||||
|
d.id_masiniclient, mc.nrinmat, mc.marca, mc.masina,
|
||||||
|
mc.anfabricatie, mc.partener
|
||||||
|
{base_from}
|
||||||
|
ORDER BY d.datai DESC, d.id_ordl DESC
|
||||||
|
OFFSET :offset ROWS FETCH NEXT :per_page ROWS ONLY
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with oracle_pool.get_connection("mariusm_test") as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(count_query, filter_params)
|
||||||
|
total = cur.fetchone()[0]
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
data_query,
|
||||||
|
{**filter_params, "offset": offset, "per_page": per_page},
|
||||||
|
)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
except oracledb.DatabaseError as e:
|
||||||
|
_handle_oracle_error(e)
|
||||||
|
|
||||||
|
comenzi: List[ComandaListItem] = []
|
||||||
|
for r in rows:
|
||||||
|
(id_ordl, nrord, datai, validat_val, inchis_fortat,
|
||||||
|
id_tip, tip_denumire,
|
||||||
|
id_mc, nrinmat, marca, masina, an, partener) = r
|
||||||
|
|
||||||
|
if id_mc:
|
||||||
|
parts = []
|
||||||
|
if marca:
|
||||||
|
parts.append(marca)
|
||||||
|
if masina:
|
||||||
|
parts.append(masina)
|
||||||
|
vehicul_str = " ".join(parts) if parts else "?"
|
||||||
|
an_str = f" ({int(an)})" if an else ""
|
||||||
|
vehicul = f"{partener or '?'} — {vehicul_str}, {nrinmat or '?'}{an_str}"
|
||||||
|
else:
|
||||||
|
vehicul = ""
|
||||||
|
|
||||||
|
comenzi.append(ComandaListItem(
|
||||||
|
id_ordl=int(id_ordl),
|
||||||
|
nrord=nrord or "",
|
||||||
|
datai=datai.strftime("%Y-%m-%d") if datai else None,
|
||||||
|
validat=int(validat_val),
|
||||||
|
inchis_fortat=int(inchis_fortat or 0),
|
||||||
|
id_tip=int(id_tip),
|
||||||
|
tip_denumire=tip_denumire or "",
|
||||||
|
vehicul=vehicul,
|
||||||
|
id_masiniclient=int(id_mc) if id_mc else None,
|
||||||
|
))
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"service_auto.get_comenzi page=%d per_page=%d total=%d",
|
||||||
|
page, per_page, total,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ComandaListResponse(
|
||||||
|
comenzi=comenzi,
|
||||||
|
total=total,
|
||||||
|
page=page,
|
||||||
|
per_page=per_page,
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ Lookup data for service_auto forms — tip deviz, masini, firme.
|
|||||||
|
|
||||||
All three endpoints are read-only and infrequently changing.
|
All three endpoints are read-only and infrequently changing.
|
||||||
"""
|
"""
|
||||||
from typing import List
|
import time
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
import oracledb
|
import oracledb
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
@@ -12,6 +13,23 @@ from shared.database.oracle_pool import oracle_pool
|
|||||||
from ..schemas.comanda import FirmaItem, MasinaClientItem, TipDevizItem
|
from ..schemas.comanda import FirmaItem, MasinaClientItem, TipDevizItem
|
||||||
from .. import logger
|
from .. import logger
|
||||||
|
|
||||||
|
# In-memory TTL cache: key → (monotonic_timestamp, value)
|
||||||
|
_cache: dict = {}
|
||||||
|
|
||||||
|
_TTL_TIP_DEVIZ = 86400 # 24 h — tip deviz changes only via DB migration
|
||||||
|
_TTL_MASINI = 300 # 5 min — vehicle inventory changes regularly
|
||||||
|
|
||||||
|
|
||||||
|
def _cache_get(key: str, ttl: float):
|
||||||
|
entry: Optional[Tuple] = _cache.get(key)
|
||||||
|
if entry and (time.monotonic() - entry[0]) < ttl:
|
||||||
|
return entry[1]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _cache_set(key: str, value) -> None:
|
||||||
|
_cache[key] = (time.monotonic(), value)
|
||||||
|
|
||||||
|
|
||||||
class LookupService:
|
class LookupService:
|
||||||
|
|
||||||
@@ -26,7 +44,7 @@ class LookupService:
|
|||||||
|
|
||||||
placeholders = ", ".join(f":id{i}" for i in range(len(company_ids)))
|
placeholders = ", ".join(f":id{i}" for i in range(len(company_ids)))
|
||||||
query = f"""
|
query = f"""
|
||||||
SELECT id_firma, firma, schema
|
SELECT id_firma, firma, schema, id_mama
|
||||||
FROM CONTAFIN_ORACLE.V_NOM_FIRME
|
FROM CONTAFIN_ORACLE.V_NOM_FIRME
|
||||||
WHERE id_firma IN ({placeholders})
|
WHERE id_firma IN ({placeholders})
|
||||||
ORDER BY id_firma
|
ORDER BY id_firma
|
||||||
@@ -43,7 +61,7 @@ class LookupService:
|
|||||||
raise HTTPException(status_code=503, detail="Eroare la încărcarea firmelor")
|
raise HTTPException(status_code=503, detail="Eroare la încărcarea firmelor")
|
||||||
|
|
||||||
return [
|
return [
|
||||||
FirmaItem(id_firma=r[0], firma=r[1], schema_name=r[2] or "")
|
FirmaItem(id_firma=r[0], firma=r[1], schema_name=r[2] or "", id_mama=r[3])
|
||||||
for r in rows
|
for r in rows
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -51,8 +69,13 @@ class LookupService:
|
|||||||
async def get_tip_deviz() -> List[TipDevizItem]:
|
async def get_tip_deviz() -> List[TipDevizItem]:
|
||||||
"""
|
"""
|
||||||
Returns all active tip deviz from MARIUSM_AUTO.DEV_TIP_DEVIZ.
|
Returns all active tip deviz from MARIUSM_AUTO.DEV_TIP_DEVIZ.
|
||||||
|
Cached in-process for 24 h (changes only via DB migration).
|
||||||
ROA_WEB has SELECT grant on this view.
|
ROA_WEB has SELECT grant on this view.
|
||||||
"""
|
"""
|
||||||
|
cached = _cache_get("tip_deviz", _TTL_TIP_DEVIZ)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
SELECT id_tip, denumire, inch_validare
|
SELECT id_tip, denumire, inch_validare
|
||||||
FROM MARIUSM_AUTO.DEV_TIP_DEVIZ
|
FROM MARIUSM_AUTO.DEV_TIP_DEVIZ
|
||||||
@@ -67,18 +90,25 @@ class LookupService:
|
|||||||
logger.error("get_tip_deviz Oracle error", exc_info=True)
|
logger.error("get_tip_deviz Oracle error", exc_info=True)
|
||||||
raise HTTPException(status_code=503, detail="Eroare la încărcarea tipurilor de deviz")
|
raise HTTPException(status_code=503, detail="Eroare la încărcarea tipurilor de deviz")
|
||||||
|
|
||||||
return [
|
result = [
|
||||||
TipDevizItem(id_tip=r[0], denumire=r[1], inch_validare=r[2] or 0)
|
TipDevizItem(id_tip=r[0], denumire=r[1], inch_validare=r[2] or 0)
|
||||||
for r in rows
|
for r in rows
|
||||||
]
|
]
|
||||||
|
_cache_set("tip_deviz", result)
|
||||||
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_masini() -> List[MasinaClientItem]:
|
async def get_masini() -> List[MasinaClientItem]:
|
||||||
"""
|
"""
|
||||||
Returns active masini from MARIUSM_AUTO.AUTO_VMASINICLIENTI.
|
Returns active masini from MARIUSM_AUTO.AUTO_VMASINICLIENTI.
|
||||||
|
Cached in-process for 5 min (vehicle inventory changes regularly).
|
||||||
ROA_WEB has SELECT grant on this view.
|
ROA_WEB has SELECT grant on this view.
|
||||||
Label format: "PARTENER — MARCA MASINA, NRINMAT (ANFABRICATIE)"
|
Label format: "PARTENER — MARCA MASINA, NRINMAT (ANFABRICATIE)"
|
||||||
"""
|
"""
|
||||||
|
cached = _cache_get("masini", _TTL_MASINI)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
SELECT id_masiniclient, nrinmat, marca, masina, anfabricatie, partener
|
SELECT id_masiniclient, nrinmat, marca, masina, anfabricatie, partener
|
||||||
FROM MARIUSM_AUTO.AUTO_VMASINICLIENTI
|
FROM MARIUSM_AUTO.AUTO_VMASINICLIENTI
|
||||||
@@ -107,4 +137,5 @@ class LookupService:
|
|||||||
label = f"{partener or '?'} — {vehicul}, {nrinmat or '?'}{an_str}"
|
label = f"{partener or '?'} — {vehicul}, {nrinmat or '?'}{an_str}"
|
||||||
result.append(MasinaClientItem(id_masiniclient=int(id_mc), label=label))
|
result.append(MasinaClientItem(id_masiniclient=int(id_mc), label=label))
|
||||||
|
|
||||||
|
_cache_set("masini", result)
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -117,7 +117,11 @@ GRANT EXECUTE ON FIRMA_NOUA.SP_CREEAZA_COMANDA_PROTOTIP TO ROA_WEB;
|
|||||||
GRANT SELECT ON FIRMA_NOUA.AUTO_VMASINICLIENTI TO ROA_WEB;
|
GRANT SELECT ON FIRMA_NOUA.AUTO_VMASINICLIENTI TO ROA_WEB;
|
||||||
GRANT SELECT ON FIRMA_NOUA.DEV_TIP_DEVIZ TO ROA_WEB;
|
GRANT SELECT ON FIRMA_NOUA.DEV_TIP_DEVIZ TO ROA_WEB;
|
||||||
GRANT SELECT ON FIRMA_NOUA.CALENDAR TO ROA_WEB;
|
GRANT SELECT ON FIRMA_NOUA.CALENDAR TO ROA_WEB;
|
||||||
-- CALENDAR folosit de shared/routes/calendar.py (period selector AppHeader)
|
-- CALENDAR: period selector AppHeader (shared/routes/calendar.py)
|
||||||
|
GRANT SELECT ON FIRMA_NOUA.DEV_ORDL TO ROA_WEB;
|
||||||
|
-- DEV_ORDL: GET /api/service-auto/comenzi (list comenzi)
|
||||||
|
GRANT SELECT ON FIRMA_NOUA.NOM_LUCRARI TO ROA_WEB;
|
||||||
|
-- NOM_LUCRARI: JOIN cu DEV_ORDL pentru nrord (get_comenzi)
|
||||||
-- adaugă orice alte SP/view-uri noi apărute de la ultimul onboarding
|
-- adaugă orice alte SP/view-uri noi apărute de la ultimul onboarding
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -197,6 +201,7 @@ ROLLBACK;
|
|||||||
| SP nou în toate schemele | `migration_YYYYMMDD_sp_noua_grants.sql` (loop V_NOM_FIRME) | 1 script per migrare |
|
| SP nou în toate schemele | `migration_YYYYMMDD_sp_noua_grants.sql` (loop V_NOM_FIRME) | 1 script per migrare |
|
||||||
| View/tabelă nouă expusă | același pattern ca SP | 1 script per migrare |
|
| View/tabelă nouă expusă | același pattern ca SP | 1 script per migrare |
|
||||||
| Expunere `CALENDAR` pentru period selector | `GRANT SELECT {SCHEMA}.CALENDAR TO ROA_WEB` per schemă | 1 linie per schemă (parte din onboarding §4.1) |
|
| Expunere `CALENDAR` pentru period selector | `GRANT SELECT {SCHEMA}.CALENDAR TO ROA_WEB` per schemă | 1 linie per schemă (parte din onboarding §4.1) |
|
||||||
|
| Expunere `DEV_ORDL` + `NOM_LUCRARI` pentru GET /comenzi | `GRANT SELECT {SCHEMA}.DEV_ORDL/NOM_LUCRARI TO ROA_WEB` per schemă | 2 linii per schemă (parte din onboarding §4.1) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
63
docs/service-auto/migrations/ff_2026_04_12_01_AUTO.sql
Normal file
63
docs/service-auto/migrations/ff_2026_04_12_01_AUTO.sql
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
-- adaugare coloana DEV_ORDL.id_sucursala + upgrade SP_CREEAZA_COMANDA_PROTOTIP
|
||||||
|
|
||||||
|
-- Rulat conectat ca schema tinta (ex: MARIUSM_AUTO), O SINGURA DATA per schema
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
IF PACK_MIGRARE.COLUMNEXIST('DEV_ORDL','ID_SUCURSALA')=0 THEN
|
||||||
|
EXECUTE IMMEDIATE 'ALTER TABLE DEV_ORDL ADD (id_sucursala NUMBER(10))';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE SP_CREEAZA_COMANDA_PROTOTIP(
|
||||||
|
p_tip IN NUMBER,
|
||||||
|
p_id_masiniclient IN NUMBER,
|
||||||
|
p_solicitari IN VARCHAR2,
|
||||||
|
p_id_firma IN NUMBER,
|
||||||
|
p_id_sucursala IN NUMBER DEFAULT NULL,
|
||||||
|
p_id_ordl OUT NUMBER,
|
||||||
|
p_nrord OUT VARCHAR2
|
||||||
|
) AS
|
||||||
|
v_id_lucrare NUMBER;
|
||||||
|
v_seq NUMBER;
|
||||||
|
v_exists NUMBER;
|
||||||
|
v_now DATE := SYSDATE;
|
||||||
|
BEGIN
|
||||||
|
v_seq := SEQ_NR_LUCRARE.NEXTVAL;
|
||||||
|
p_nrord := 'P' || LPAD(p_id_firma, 2, '0') || '-' || v_seq;
|
||||||
|
|
||||||
|
SELECT COUNT(*) INTO v_exists
|
||||||
|
FROM NOM_LUCRARI
|
||||||
|
WHERE sters = 0 AND nrord = p_nrord;
|
||||||
|
IF v_exists > 0 THEN
|
||||||
|
RAISE_APPLICATION_ERROR(-20001,
|
||||||
|
'Mai exista o comanda cu numarul ' || p_nrord);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO NOM_LUCRARI (nrord, id_mod)
|
||||||
|
VALUES (p_nrord, 1200)
|
||||||
|
RETURNING id_lucrare INTO v_id_lucrare;
|
||||||
|
|
||||||
|
INSERT INTO DEV_ORDL (
|
||||||
|
an, luna,
|
||||||
|
id_lucrare,
|
||||||
|
datai, dataoraad,
|
||||||
|
id_util_ad,
|
||||||
|
id_masiniclient,
|
||||||
|
id_tip,
|
||||||
|
solicitari_client,
|
||||||
|
id_sucursala
|
||||||
|
) VALUES (
|
||||||
|
EXTRACT(YEAR FROM v_now), EXTRACT(MONTH FROM v_now),
|
||||||
|
v_id_lucrare,
|
||||||
|
v_now, v_now,
|
||||||
|
0,
|
||||||
|
p_id_masiniclient,
|
||||||
|
p_tip,
|
||||||
|
p_solicitari,
|
||||||
|
p_id_sucursala
|
||||||
|
) RETURNING id_ordl INTO p_id_ordl;
|
||||||
|
END SP_CREEAZA_COMANDA_PROTOTIP;
|
||||||
|
/
|
||||||
|
|
||||||
|
exec pack_migrare.UpdateVersiune('ff_2026_04_12_01_AUTO'); commit;
|
||||||
24
docs/service-auto/onboarding_roa_web.sql
Normal file
24
docs/service-auto/onboarding_roa_web.sql
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
-- =============================================================================
|
||||||
|
-- File purpose : Script de onboarding ROA_WEB pentru o schemă nouă
|
||||||
|
-- When to run : Rulat ca CONTAFIN_ORACLE după impdp pentru fiecare firmă nouă
|
||||||
|
-- Usage : Înlocuiește :SCHEMA_NAME cu schema reală (ex: MARIUSM_AUTO)
|
||||||
|
-- Version : 2026-04-12
|
||||||
|
-- Prerequisite : ROA_WEB user creat (onboarding_roa_web_user.sql)
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
GRANT EXECUTE ON :SCHEMA_NAME.SP_CREEAZA_COMANDA_PROTOTIP TO ROA_WEB;
|
||||||
|
GRANT SELECT ON :SCHEMA_NAME.AUTO_VMASINICLIENTI TO ROA_WEB;
|
||||||
|
GRANT SELECT ON :SCHEMA_NAME.DEV_TIP_DEVIZ TO ROA_WEB;
|
||||||
|
GRANT SELECT ON :SCHEMA_NAME.CALENDAR TO ROA_WEB; -- period selector AppHeader
|
||||||
|
GRANT SELECT ON :SCHEMA_NAME.DEV_ORDL TO ROA_WEB; -- GET /api/service-auto/comenzi
|
||||||
|
GRANT SELECT ON :SCHEMA_NAME.NOM_LUCRARI TO ROA_WEB; -- JOIN cu DEV_ORDL pentru nrord
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- ROA_WEB user creation (one-time, run as SYS or CONTAFIN_ORACLE)
|
||||||
|
-- =============================================================================
|
||||||
|
-- Rulat O SINGURĂ DATĂ la setup inițial, NU pentru fiecare firmă nouă.
|
||||||
|
-- Pentru fiecare firmă nouă se rulează doar secțiunea de GRANT-uri de mai sus.
|
||||||
|
|
||||||
|
CREATE USER ROA_WEB IDENTIFIED BY "<REPLACE_WITH_STRONG_PASSWORD_FROM_VAULT>";
|
||||||
|
GRANT CREATE SESSION TO ROA_WEB;
|
||||||
|
-- Fără alte privilegii sistem. Accesul la date = exclusiv prin granturi per-obiect.
|
||||||
@@ -63,6 +63,24 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SERVICE AUTO Section -->
|
||||||
|
<div class="menu-section">
|
||||||
|
<h3 class="menu-title">Service Auto</h3>
|
||||||
|
<ul class="menu-list">
|
||||||
|
<li class="menu-item" v-for="item in serviceAutoItems" :key="item.to">
|
||||||
|
<router-link
|
||||||
|
:to="item.to"
|
||||||
|
class="menu-link"
|
||||||
|
:class="{ active: isActive(item.to, item.exactMatch) }"
|
||||||
|
@click="closeMenu"
|
||||||
|
>
|
||||||
|
<i :class="['menu-icon', item.icon]"></i>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ADMINISTRARE Section -->
|
<!-- ADMINISTRARE Section -->
|
||||||
<div class="menu-section">
|
<div class="menu-section">
|
||||||
<h3 class="menu-title">Administrare</h3>
|
<h3 class="menu-title">Administrare</h3>
|
||||||
@@ -144,6 +162,12 @@ export default {
|
|||||||
{ to: '/reports/detailed-invoices', icon: 'pi pi-list', label: 'Facturi Detaliate', exactMatch: true }
|
{ to: '/reports/detailed-invoices', icon: 'pi pi-list', label: 'Facturi Detaliate', exactMatch: true }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// SERVICE AUTO: Comenzi, Comandă Nouă
|
||||||
|
const serviceAutoItems = ref([
|
||||||
|
{ to: '/service-auto/comenzi', icon: 'pi pi-list', label: 'Comenzi', exactMatch: true },
|
||||||
|
{ to: '/service-auto/comanda-noua', icon: 'pi pi-plus', label: 'Comandă Nouă', exactMatch: true },
|
||||||
|
]);
|
||||||
|
|
||||||
// ADMINISTRARE: Setări
|
// ADMINISTRARE: Setări
|
||||||
const administrareItems = ref([
|
const administrareItems = ref([
|
||||||
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări', exactMatch: false }
|
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări', exactMatch: false }
|
||||||
@@ -179,6 +203,7 @@ export default {
|
|||||||
principaleItems,
|
principaleItems,
|
||||||
rapoarteItems,
|
rapoarteItems,
|
||||||
analizeItems,
|
analizeItems,
|
||||||
|
serviceAutoItems,
|
||||||
administrareItems,
|
administrareItems,
|
||||||
isActive,
|
isActive,
|
||||||
closeMenu,
|
closeMenu,
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ export default {
|
|||||||
getFirme: () => api.get('/firme'),
|
getFirme: () => api.get('/firme'),
|
||||||
getTipDeviz: () => api.get('/tip-deviz'),
|
getTipDeviz: () => api.get('/tip-deviz'),
|
||||||
getMasini: () => api.get('/masini'),
|
getMasini: () => api.get('/masini'),
|
||||||
|
getComenzi: (params) => api.get('/comenzi', { params }),
|
||||||
creeazaComanda: (data) => api.post('/comenzi', data),
|
creeazaComanda: (data) => api.post('/comenzi', data),
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/modules/service-auto/stores/sharedStores.js
Normal file
15
src/modules/service-auto/stores/sharedStores.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Service Auto Module - Shared Store Instances
|
||||||
|
*
|
||||||
|
* Instantiates the shared stores (auth, companies) with the
|
||||||
|
* Service Auto module's API service.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createAuthStore } from '@shared/stores/auth'
|
||||||
|
import { createCompaniesStore } from '@shared/stores/companies'
|
||||||
|
import api from '../services/api.js'
|
||||||
|
|
||||||
|
const resetAllStores = () => {}
|
||||||
|
|
||||||
|
export const useAuthStore = createAuthStore(api, { onLogout: resetAllStores })
|
||||||
|
export const useCompanyStore = createCompaniesStore(api, useAuthStore)
|
||||||
@@ -104,8 +104,10 @@ import Textarea from 'primevue/textarea'
|
|||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import Toast from 'primevue/toast'
|
import Toast from 'primevue/toast'
|
||||||
import serviceAutoApi from '../services/api.js'
|
import serviceAutoApi from '../services/api.js'
|
||||||
|
import { useCompanyStore } from '../stores/sharedStores.js'
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const companyStore = useCompanyStore()
|
||||||
const clientDropdownRef = ref(null)
|
const clientDropdownRef = ref(null)
|
||||||
const isSubmitting = ref(false)
|
const isSubmitting = ref(false)
|
||||||
|
|
||||||
@@ -131,9 +133,13 @@ async function loadLookups() {
|
|||||||
|
|
||||||
if (firmeRes.status === 'fulfilled') {
|
if (firmeRes.status === 'fulfilled') {
|
||||||
firme.value = firmeRes.value.data
|
firme.value = firmeRes.value.data
|
||||||
// Default: first company
|
// Default: selected company from AppHeader store, fallback to first
|
||||||
if (firme.value.length > 0 && form.value.id_firma === null) {
|
if (firme.value.length > 0 && form.value.id_firma === null) {
|
||||||
form.value.id_firma = firme.value[0].id_firma
|
const selected = companyStore.selectedCompany
|
||||||
|
const defaultFirma = firme.value.find(f => f.id_firma === selected?.id_firma) || firme.value[0]
|
||||||
|
if (defaultFirma) {
|
||||||
|
form.value.id_firma = defaultFirma.id_firma
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.add({ severity: 'warn', summary: 'Firme', detail: 'Nu s-au putut încărca firmele', life: 4000 })
|
toast.add({ severity: 'warn', summary: 'Firme', detail: 'Nu s-au putut încărca firmele', life: 4000 })
|
||||||
@@ -169,6 +175,9 @@ const emptyForm = () => ({
|
|||||||
|
|
||||||
const form = ref(emptyForm())
|
const form = ref(emptyForm())
|
||||||
|
|
||||||
|
const selectedFirma = computed(() => firme.value.find(f => f.id_firma === form.value.id_firma) || null)
|
||||||
|
const idSucursala = computed(() => selectedFirma.value?.id_mama != null ? form.value.id_firma : null)
|
||||||
|
|
||||||
const isFormValid = computed(() =>
|
const isFormValid = computed(() =>
|
||||||
form.value.id_firma !== null &&
|
form.value.id_firma !== null &&
|
||||||
form.value.tip_id !== null &&
|
form.value.tip_id !== null &&
|
||||||
@@ -188,6 +197,7 @@ async function submitComanda() {
|
|||||||
id_masiniclient: form.value.id_masiniclient,
|
id_masiniclient: form.value.id_masiniclient,
|
||||||
solicitari: form.value.solicitari.trim(),
|
solicitari: form.value.solicitari.trim(),
|
||||||
id_firma: form.value.id_firma,
|
id_firma: form.value.id_firma,
|
||||||
|
id_sucursala: idSucursala.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
|
|||||||
275
src/modules/service-auto/views/ComenziBrowseView.vue
Normal file
275
src/modules/service-auto/views/ComenziBrowseView.vue
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<Toast />
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="card-header" style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--space-sm);">
|
||||||
|
<h2 style="font-size: var(--text-xl); font-weight: var(--font-semibold); margin: 0;">
|
||||||
|
Comenzi Service
|
||||||
|
</h2>
|
||||||
|
<router-link to="/service-auto/comanda-noua">
|
||||||
|
<Button label="Comandă Nouă" icon="pi pi-plus" size="small" />
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filters row -->
|
||||||
|
<div class="card-body" style="padding-bottom: 0;">
|
||||||
|
<div class="filters-row">
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Status</label>
|
||||||
|
<Dropdown
|
||||||
|
v-model="filters.validat"
|
||||||
|
:options="statusOptions"
|
||||||
|
option-label="label"
|
||||||
|
option-value="value"
|
||||||
|
placeholder="Toate"
|
||||||
|
class="w-full"
|
||||||
|
@change="resetAndLoad"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">De la</label>
|
||||||
|
<Calendar
|
||||||
|
v-model="filters.data_de_la"
|
||||||
|
date-format="dd.mm.yy"
|
||||||
|
placeholder="—"
|
||||||
|
class="w-full"
|
||||||
|
@date-select="resetAndLoad"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Până la</label>
|
||||||
|
<Calendar
|
||||||
|
v-model="filters.data_pana_la"
|
||||||
|
date-format="dd.mm.yy"
|
||||||
|
placeholder="—"
|
||||||
|
class="w-full"
|
||||||
|
@date-select="resetAndLoad"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group filter-actions">
|
||||||
|
<Button
|
||||||
|
label="Resetează"
|
||||||
|
icon="pi pi-filter-slash"
|
||||||
|
severity="secondary"
|
||||||
|
outlined
|
||||||
|
size="small"
|
||||||
|
@click="clearFilters"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="card-body">
|
||||||
|
<DataTable
|
||||||
|
:value="comenzi"
|
||||||
|
:lazy="true"
|
||||||
|
:paginator="true"
|
||||||
|
:rows="perPage"
|
||||||
|
:total-records="total"
|
||||||
|
:loading="loading"
|
||||||
|
class="p-datatable-sm"
|
||||||
|
striped-rows
|
||||||
|
@page="onPage"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<div style="text-align: center; padding: var(--space-xl); color: var(--text-color-secondary);">
|
||||||
|
Nicio comandă găsită
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<Column field="nrord" header="Nr. Ord." style="min-width: 100px;" />
|
||||||
|
|
||||||
|
<Column field="datai" header="Data" style="min-width: 100px;" />
|
||||||
|
|
||||||
|
<Column header="Status" style="min-width: 110px;">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<span :class="['status-badge', statusClass(data)]">
|
||||||
|
{{ statusLabel(data) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column header="Tip" style="min-width: 130px;">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ data.tip_denumire }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column header="Client / Vehicul" style="min-width: 240px;">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<span style="color: var(--text-color);">{{ data.vehicul || '—' }}</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
import DataTable from 'primevue/datatable'
|
||||||
|
import Column from 'primevue/column'
|
||||||
|
import Dropdown from 'primevue/dropdown'
|
||||||
|
import Calendar from 'primevue/calendar'
|
||||||
|
import Button from 'primevue/button'
|
||||||
|
import Toast from 'primevue/toast'
|
||||||
|
import serviceAutoApi from '../services/api.js'
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
// ─── State ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const comenzi = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
const page = ref(1)
|
||||||
|
const perPage = ref(20)
|
||||||
|
|
||||||
|
const filters = ref({
|
||||||
|
validat: null,
|
||||||
|
data_de_la: null,
|
||||||
|
data_pana_la: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: 'Toate', value: null },
|
||||||
|
{ label: 'Deschisă', value: 0 },
|
||||||
|
{ label: 'Validată', value: 1 },
|
||||||
|
]
|
||||||
|
|
||||||
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const fmtDate = (d) => d ? d.toISOString().slice(0, 10) : undefined
|
||||||
|
|
||||||
|
function statusLabel(row) {
|
||||||
|
if (row.inchis_fortat) return 'Arhivată'
|
||||||
|
return row.validat ? 'Validată' : 'Deschisă'
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusClass(row) {
|
||||||
|
if (row.inchis_fortat) return 'status-archived'
|
||||||
|
return row.validat ? 'status-validated' : 'status-open'
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Data loading ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function loadComenzi() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
page: page.value,
|
||||||
|
per_page: perPage.value,
|
||||||
|
}
|
||||||
|
if (filters.value.validat !== null) params.validat = filters.value.validat
|
||||||
|
if (filters.value.data_de_la) params.data_de_la = fmtDate(filters.value.data_de_la)
|
||||||
|
if (filters.value.data_pana_la) params.data_pana_la = fmtDate(filters.value.data_pana_la)
|
||||||
|
|
||||||
|
const { data } = await serviceAutoApi.getComenzi(params)
|
||||||
|
comenzi.value = data.comenzi
|
||||||
|
total.value = data.total
|
||||||
|
} catch (err) {
|
||||||
|
const status = err.response?.status
|
||||||
|
if (status === 503) {
|
||||||
|
toast.add({ severity: 'error', summary: 'Eroare conexiune', detail: 'Serviciul bazei de date nu este disponibil', life: 5000 })
|
||||||
|
} else {
|
||||||
|
toast.add({ severity: 'error', summary: 'Eroare', detail: 'Nu s-au putut încărca comenzile', life: 4000 })
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAndLoad() {
|
||||||
|
page.value = 1
|
||||||
|
loadComenzi()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFilters() {
|
||||||
|
filters.value = { validat: null, data_de_la: null, data_pana_la: null }
|
||||||
|
page.value = 1
|
||||||
|
loadComenzi()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPage(event) {
|
||||||
|
page.value = event.page + 1 // PrimeVue is 0-indexed
|
||||||
|
perPage.value = event.rows
|
||||||
|
loadComenzi()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadComenzi)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.filters-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-md);
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group.filter-actions {
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-label {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status badges */
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px var(--space-sm);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-open {
|
||||||
|
background: var(--green-50);
|
||||||
|
color: var(--green-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-validated {
|
||||||
|
background: var(--blue-50);
|
||||||
|
color: var(--blue-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-archived {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .status-open {
|
||||||
|
background: var(--green-900);
|
||||||
|
color: var(--green-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .status-validated {
|
||||||
|
background: var(--blue-900);
|
||||||
|
color: var(--blue-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .status-archived {
|
||||||
|
background: var(--surface-100);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -140,6 +140,12 @@ const routes = [
|
|||||||
path: '/service-auto',
|
path: '/service-auto',
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: 'comenzi',
|
||||||
|
name: 'Comenzi',
|
||||||
|
component: () => import('@/modules/service-auto/views/ComenziBrowseView.vue'),
|
||||||
|
meta: { requiresAuth: true, title: 'Comenzi - Service Auto' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'comanda-noua',
|
path: 'comanda-noua',
|
||||||
name: 'ComandaNoua',
|
name: 'ComandaNoua',
|
||||||
|
|||||||
Reference in New Issue
Block a user