Migrare completă de la SP_CREEAZA_COMANDA_PROTOTIP la PACK_AUTO.dev_adauga_lucrare (18 params). Formular ComandaNoua extins cu toate câmpurile din SP: observații, defecțiuni, km, ore motor, termen, nr. dosar. - schema: solicitari → observatii (opțional); adăugat defectiuni, km_int, ore_functionare, nr_dosar, termen - service: callproc cablat pe câmpurile noi; pc_nr cu milisecunde (evită colizii sub-secundă) - error mapper: range 20001→20000 (ORA-20000 era neacoperit → 500 în loc de 422) - onboarding_roa_web.sql: grant pe PACK_AUTO (înlocuiește SP prototip) - ComandaNoua.vue: InputNumber km/ore, Calendar termen, Textarea defecțiuni, InputText nr_dosar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
247 lines
9.7 KiB
Python
247 lines
9.7 KiB
Python
import re
|
|
from datetime import date, datetime
|
|
from typing import List, NoReturn, Optional
|
|
|
|
import oracledb
|
|
from fastapi import HTTPException
|
|
from shared.database.oracle_pool import oracle_pool
|
|
from ..schemas.comanda import (
|
|
ComandaListItem, ComandaListResponse, ComandaRequest, ComandaResponse,
|
|
)
|
|
from .. import logger
|
|
|
|
_SCHEMA = "MARIUSM_AUTO"
|
|
|
|
_MAX_PER_PAGE = 100
|
|
|
|
|
|
def _handle_oracle_error(e: Exception) -> NoReturn:
|
|
"""
|
|
Map Oracle error codes to FastAPI HTTPExceptions. Always raises.
|
|
|
|
Code ranges:
|
|
20000-20999 → 422 Unprocessable (business rule errors from RAISE_APPLICATION_ERROR)
|
|
12541/12170/12154/12560 → 503 Service Unavailable (Oracle unreachable / network)
|
|
1017 → 500 + CRITICAL log (bad credentials — config error)
|
|
942 → 500 + CRITICAL log (missing object/grant — deployment error)
|
|
* → 500 + ERROR log (unexpected)
|
|
"""
|
|
err = e.args[0]
|
|
code = getattr(err, "code", 0)
|
|
raw_message = getattr(err, "message", str(e))
|
|
|
|
if 20000 <= code <= 20999:
|
|
# Strip "ORA-2xxxx: " prefix injected by Oracle; expose the business message only
|
|
clean = re.sub(r"^ORA-\d+:\s*", "", raw_message).strip()
|
|
raise HTTPException(status_code=422, detail=clean)
|
|
|
|
if code == 1438:
|
|
# ORA-01438: value larger than specified precision — bad input ID (e.g. id_masiniclient out of range)
|
|
raise HTTPException(status_code=422, detail="Valoare invalidă pentru câmp (ID prea mare)")
|
|
|
|
if code in (12541, 12170, 12154, 12560):
|
|
raise HTTPException(
|
|
status_code=503,
|
|
detail="Serviciul bazei de date e temporar indisponibil",
|
|
)
|
|
|
|
if code == 1017:
|
|
logger.critical("Oracle credentials rejected (ORA-01017)", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail="Eroare de configurare. Contactați administratorul.",
|
|
)
|
|
|
|
if code == 942:
|
|
logger.critical("Oracle object not found or grant missing (ORA-00942)", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail="Eroare internă. Contactați administratorul.",
|
|
)
|
|
|
|
logger.error("Unexpected Oracle error ORA-%05d", code, exc_info=True)
|
|
raise HTTPException(status_code=500, detail="Eroare internă neașteptată")
|
|
|
|
|
|
class ComandaService:
|
|
@staticmethod
|
|
async def creeaza_comanda(
|
|
data: ComandaRequest,
|
|
username: str,
|
|
user_id: Optional[int] = None,
|
|
) -> ComandaResponse:
|
|
now = datetime.now()
|
|
# pcNr serves as NOM_LUCRARI.NRORD — must be unique; timestamp gives collision-safe value.
|
|
pc_nr = f"W{now.strftime('%Y%m%d%H%M%S')}{now.microsecond // 1000:03d}"
|
|
|
|
logger.info(
|
|
"service_auto.create_comanda START",
|
|
extra={
|
|
"user": username,
|
|
"tip": data.tip_id,
|
|
"client_id": data.id_masiniclient,
|
|
"id_firma": data.id_firma,
|
|
"pc_nr": pc_nr,
|
|
"km": data.km_int,
|
|
"ore": data.ore_functionare,
|
|
},
|
|
)
|
|
|
|
async with oracle_pool.get_connection("mariusm_test") as connection:
|
|
try:
|
|
with connection.cursor() as cursor:
|
|
# pnIdOrdl is IN OUT — setvalue(0, 0) sets the IN side to 0;
|
|
# Oracle overwrites it with the new DEV_ORDL.ID_ORDL.
|
|
out_id_ordl = cursor.var(oracledb.NUMBER)
|
|
out_id_ordl.setvalue(0, 0)
|
|
|
|
cursor.callproc(
|
|
f"{_SCHEMA}.PACK_AUTO.dev_adauga_lucrare",
|
|
[
|
|
_SCHEMA, # v_gcs IN VARCHAR2
|
|
now.year, # tnan IN NUMBER
|
|
now.month, # tnluna IN NUMBER
|
|
user_id or 0, # tnIdUtil IN NUMBER (Oracle ID_UTIL)
|
|
pc_nr, # pcNr IN VARCHAR2 (NOM_LUCRARI.NRORD)
|
|
None, # pnIdInsp IN NUMBER
|
|
None, # pnIdAsig IN NUMBER
|
|
data.nr_dosar or "", # pcNrDosar IN VARCHAR2
|
|
data.id_masiniclient, # pnIdMC IN NUMBER
|
|
data.km_int, # pnKmInt IN NUMBER
|
|
data.ore_functionare, # pnOreFct IN NUMBER (≥0; NOT NULL in DEV_MASINICLIENTI)
|
|
data.termen, # pdTermen IN DATE
|
|
data.tip_id, # pnTipCom IN NUMBER
|
|
None, # pcSirIdOperatii IN VARCHAR2 — MUST be None, NOT ''
|
|
data.observatii or None, # pcObservatii IN VARCHAR2 DEFAULT NULL
|
|
data.defectiuni or None, # pcDefectiuni IN VARCHAR2 DEFAULT NULL
|
|
0, # pnIdPartRef IN NUMBER (decode(0) → NULL inside SP)
|
|
out_id_ordl, # pnIdOrdl IN OUT NUMBER
|
|
],
|
|
)
|
|
|
|
connection.commit()
|
|
|
|
id_ordl = int(out_id_ordl.getvalue())
|
|
except oracledb.DatabaseError as e:
|
|
try:
|
|
connection.rollback()
|
|
except Exception:
|
|
pass # connection may be dead on network errors; ignore
|
|
_handle_oracle_error(e)
|
|
|
|
logger.info(
|
|
"service_auto.create_comanda OK",
|
|
extra={"user": username, "id_ordl": id_ordl, "nrord": pc_nr},
|
|
)
|
|
|
|
return ComandaResponse(
|
|
id_ordl=id_ordl,
|
|
nrord=pc_nr,
|
|
mesaj=f"Comanda {pc_nr} 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,
|
|
)
|