feat(service-auto): phase 3 — PACK_AUTO callproc + câmpuri extinse formular
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>
This commit is contained in:
@@ -81,6 +81,7 @@ async def creeaza_comanda(
|
|||||||
return await ComandaService.creeaza_comanda(
|
return await ComandaService.creeaza_comanda(
|
||||||
data=data,
|
data=data,
|
||||||
username=current_user.username,
|
username=current_user.username,
|
||||||
|
user_id=current_user.user_id,
|
||||||
)
|
)
|
||||||
except NotImplementedError as e:
|
except NotImplementedError as e:
|
||||||
raise HTTPException(status_code=501, detail=str(e))
|
raise HTTPException(status_code=501, detail=str(e))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from datetime import date
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -6,9 +7,14 @@ from pydantic import BaseModel
|
|||||||
class ComandaRequest(BaseModel):
|
class ComandaRequest(BaseModel):
|
||||||
tip_id: int
|
tip_id: int
|
||||||
id_masiniclient: int
|
id_masiniclient: int
|
||||||
solicitari: str
|
|
||||||
id_firma: int
|
id_firma: int
|
||||||
id_sucursala: Optional[int] = None
|
id_sucursala: Optional[int] = None
|
||||||
|
observatii: str = ""
|
||||||
|
defectiuni: Optional[str] = None
|
||||||
|
km_int: int = 0
|
||||||
|
ore_functionare: int = 0
|
||||||
|
nr_dosar: str = ""
|
||||||
|
termen: Optional[date] = None
|
||||||
|
|
||||||
|
|
||||||
class ComandaResponse(BaseModel):
|
class ComandaResponse(BaseModel):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
from datetime import date
|
from datetime import date, datetime
|
||||||
from typing import List, NoReturn, Optional
|
from typing import List, NoReturn, Optional
|
||||||
|
|
||||||
import oracledb
|
import oracledb
|
||||||
@@ -10,6 +10,8 @@ from ..schemas.comanda import (
|
|||||||
)
|
)
|
||||||
from .. import logger
|
from .. import logger
|
||||||
|
|
||||||
|
_SCHEMA = "MARIUSM_AUTO"
|
||||||
|
|
||||||
_MAX_PER_PAGE = 100
|
_MAX_PER_PAGE = 100
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ def _handle_oracle_error(e: Exception) -> NoReturn:
|
|||||||
Map Oracle error codes to FastAPI HTTPExceptions. Always raises.
|
Map Oracle error codes to FastAPI HTTPExceptions. Always raises.
|
||||||
|
|
||||||
Code ranges:
|
Code ranges:
|
||||||
20001-20999 → 422 Unprocessable (business rule errors from RAISE_APPLICATION_ERROR)
|
20000-20999 → 422 Unprocessable (business rule errors from RAISE_APPLICATION_ERROR)
|
||||||
12541/12170/12154/12560 → 503 Service Unavailable (Oracle unreachable / network)
|
12541/12170/12154/12560 → 503 Service Unavailable (Oracle unreachable / network)
|
||||||
1017 → 500 + CRITICAL log (bad credentials — config error)
|
1017 → 500 + CRITICAL log (bad credentials — config error)
|
||||||
942 → 500 + CRITICAL log (missing object/grant — deployment error)
|
942 → 500 + CRITICAL log (missing object/grant — deployment error)
|
||||||
@@ -28,7 +30,7 @@ def _handle_oracle_error(e: Exception) -> NoReturn:
|
|||||||
code = getattr(err, "code", 0)
|
code = getattr(err, "code", 0)
|
||||||
raw_message = getattr(err, "message", str(e))
|
raw_message = getattr(err, "message", str(e))
|
||||||
|
|
||||||
if 20001 <= code <= 20999:
|
if 20000 <= code <= 20999:
|
||||||
# Strip "ORA-2xxxx: " prefix injected by Oracle; expose the business message only
|
# Strip "ORA-2xxxx: " prefix injected by Oracle; expose the business message only
|
||||||
clean = re.sub(r"^ORA-\d+:\s*", "", raw_message).strip()
|
clean = re.sub(r"^ORA-\d+:\s*", "", raw_message).strip()
|
||||||
raise HTTPException(status_code=422, detail=clean)
|
raise HTTPException(status_code=422, detail=clean)
|
||||||
@@ -66,7 +68,12 @@ class ComandaService:
|
|||||||
async def creeaza_comanda(
|
async def creeaza_comanda(
|
||||||
data: ComandaRequest,
|
data: ComandaRequest,
|
||||||
username: str,
|
username: str,
|
||||||
|
user_id: Optional[int] = None,
|
||||||
) -> ComandaResponse:
|
) -> 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(
|
logger.info(
|
||||||
"service_auto.create_comanda START",
|
"service_auto.create_comanda START",
|
||||||
extra={
|
extra={
|
||||||
@@ -74,32 +81,47 @@ class ComandaService:
|
|||||||
"tip": data.tip_id,
|
"tip": data.tip_id,
|
||||||
"client_id": data.id_masiniclient,
|
"client_id": data.id_masiniclient,
|
||||||
"id_firma": data.id_firma,
|
"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:
|
async with oracle_pool.get_connection("mariusm_test") as connection:
|
||||||
try:
|
try:
|
||||||
with connection.cursor() as cursor:
|
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 = cursor.var(oracledb.NUMBER)
|
||||||
out_nrord = cursor.var(oracledb.STRING)
|
out_id_ordl.setvalue(0, 0)
|
||||||
|
|
||||||
cursor.callproc(
|
cursor.callproc(
|
||||||
"MARIUSM_AUTO.SP_CREEAZA_COMANDA_PROTOTIP",
|
f"{_SCHEMA}.PACK_AUTO.dev_adauga_lucrare",
|
||||||
[
|
[
|
||||||
data.tip_id, # p_tip IN NUMBER
|
_SCHEMA, # v_gcs IN VARCHAR2
|
||||||
data.id_masiniclient, # p_id_masiniclient IN NUMBER
|
now.year, # tnan IN NUMBER
|
||||||
data.solicitari, # p_solicitari IN VARCHAR2
|
now.month, # tnluna IN NUMBER
|
||||||
data.id_firma, # p_id_firma IN NUMBER
|
user_id or 0, # tnIdUtil IN NUMBER (Oracle ID_UTIL)
|
||||||
data.id_sucursala, # p_id_sucursala IN NUMBER (None for parent firm)
|
pc_nr, # pcNr IN VARCHAR2 (NOM_LUCRARI.NRORD)
|
||||||
out_id_ordl, # p_id_ordl OUT NUMBER
|
None, # pnIdInsp IN NUMBER
|
||||||
out_nrord, # p_nrord OUT VARCHAR2
|
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()
|
connection.commit()
|
||||||
|
|
||||||
id_ordl = int(out_id_ordl.getvalue())
|
id_ordl = int(out_id_ordl.getvalue())
|
||||||
nrord = out_nrord.getvalue() or ""
|
|
||||||
except oracledb.DatabaseError as e:
|
except oracledb.DatabaseError as e:
|
||||||
try:
|
try:
|
||||||
connection.rollback()
|
connection.rollback()
|
||||||
@@ -109,13 +131,13 @@ class ComandaService:
|
|||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"service_auto.create_comanda OK",
|
"service_auto.create_comanda OK",
|
||||||
extra={"user": username, "id_ordl": id_ordl, "nrord": nrord},
|
extra={"user": username, "id_ordl": id_ordl, "nrord": pc_nr},
|
||||||
)
|
)
|
||||||
|
|
||||||
return ComandaResponse(
|
return ComandaResponse(
|
||||||
id_ordl=id_ordl,
|
id_ordl=id_ordl,
|
||||||
nrord=nrord,
|
nrord=pc_nr,
|
||||||
mesaj=f"Comanda {nrord} creata cu succes.",
|
mesaj=f"Comanda {pc_nr} creata cu succes.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
-- Prerequisite : ROA_WEB user creat (onboarding_roa_web_user.sql)
|
-- Prerequisite : ROA_WEB user creat (onboarding_roa_web_user.sql)
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
|
|
||||||
GRANT EXECUTE ON :SCHEMA_NAME.SP_CREEAZA_COMANDA_PROTOTIP TO ROA_WEB;
|
GRANT EXECUTE ON :SCHEMA_NAME.PACK_AUTO TO ROA_WEB;
|
||||||
GRANT SELECT ON :SCHEMA_NAME.AUTO_VMASINICLIENTI 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.DEV_TIP_DEVIZ TO ROA_WEB;
|
||||||
GRANT SELECT ON :SCHEMA_NAME.CALENDAR TO ROA_WEB; -- period selector AppHeader
|
GRANT SELECT ON :SCHEMA_NAME.CALENDAR TO ROA_WEB; -- period selector AppHeader
|
||||||
|
|||||||
@@ -65,20 +65,95 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Operații solicitate -->
|
<!-- Observații client -->
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label style="font-weight: var(--font-medium); font-size: var(--text-sm); color: var(--text-color-secondary);">
|
<label style="font-weight: var(--font-medium); font-size: var(--text-sm); color: var(--text-color-secondary);">
|
||||||
Operații solicitate *
|
Observații client
|
||||||
</label>
|
</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
v-model="form.solicitari"
|
v-model="form.observatii"
|
||||||
rows="4"
|
rows="3"
|
||||||
placeholder="Descrieți operațiile solicitate de client..."
|
placeholder="Solicitări, observații client..."
|
||||||
:disabled="isSubmitting"
|
:disabled="isSubmitting"
|
||||||
style="width: 100%; resize: vertical;"
|
style="width: 100%; resize: vertical;"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Defecțiuni constatate la recepție -->
|
||||||
|
<div class="field">
|
||||||
|
<label style="font-weight: var(--font-medium); font-size: var(--text-sm); color: var(--text-color-secondary);">
|
||||||
|
Defecțiuni constatate la recepție
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
v-model="form.defectiuni"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Defecțiuni observate la preluarea vehiculului..."
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
style="width: 100%; resize: vertical;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Km + Ore funcționare (same row) -->
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md);">
|
||||||
|
<div class="field">
|
||||||
|
<label style="font-weight: var(--font-medium); font-size: var(--text-sm); color: var(--text-color-secondary);">
|
||||||
|
Kilometraj la recepție
|
||||||
|
</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model="form.km_int"
|
||||||
|
:min="0"
|
||||||
|
:max="9999999"
|
||||||
|
:use-grouping="true"
|
||||||
|
suffix=" km"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label style="font-weight: var(--font-medium); font-size: var(--text-sm); color: var(--text-color-secondary);">
|
||||||
|
Ore funcționare motor
|
||||||
|
</label>
|
||||||
|
<InputNumber
|
||||||
|
v-model="form.ore_functionare"
|
||||||
|
:min="0"
|
||||||
|
:max="999999"
|
||||||
|
:use-grouping="true"
|
||||||
|
suffix=" ore"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Termen estimat -->
|
||||||
|
<div class="field">
|
||||||
|
<label style="font-weight: var(--font-medium); font-size: var(--text-sm); color: var(--text-color-secondary);">
|
||||||
|
Termen estimat finalizare
|
||||||
|
</label>
|
||||||
|
<Calendar
|
||||||
|
v-model="form.termen"
|
||||||
|
date-format="dd.mm.yy"
|
||||||
|
placeholder="Selectează data..."
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
:min-date="new Date()"
|
||||||
|
show-icon
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Număr dosar asigurare -->
|
||||||
|
<div class="field">
|
||||||
|
<label style="font-weight: var(--font-medium); font-size: var(--text-sm); color: var(--text-color-secondary);">
|
||||||
|
Nr. dosar asigurare
|
||||||
|
</label>
|
||||||
|
<InputText
|
||||||
|
v-model="form.nr_dosar"
|
||||||
|
placeholder="Completați dacă e comandă de asigurare..."
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Submit -->
|
<!-- Submit -->
|
||||||
<div style="display: flex; justify-content: flex-end; padding-top: var(--space-sm);">
|
<div style="display: flex; justify-content: flex-end; padding-top: var(--space-sm);">
|
||||||
<Button
|
<Button
|
||||||
@@ -103,6 +178,9 @@ import Dropdown from 'primevue/dropdown'
|
|||||||
import Textarea from 'primevue/textarea'
|
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 InputNumber from 'primevue/inputnumber'
|
||||||
|
import InputText from 'primevue/inputtext'
|
||||||
|
import Calendar from 'primevue/calendar'
|
||||||
import serviceAutoApi from '../services/api.js'
|
import serviceAutoApi from '../services/api.js'
|
||||||
import { useCompanyStore } from '../stores/sharedStores.js'
|
import { useCompanyStore } from '../stores/sharedStores.js'
|
||||||
|
|
||||||
@@ -170,7 +248,12 @@ const emptyForm = () => ({
|
|||||||
id_firma: null,
|
id_firma: null,
|
||||||
tip_id: null,
|
tip_id: null,
|
||||||
id_masiniclient: null,
|
id_masiniclient: null,
|
||||||
solicitari: '',
|
observatii: '',
|
||||||
|
defectiuni: '',
|
||||||
|
km_int: 0,
|
||||||
|
ore_functionare: 0,
|
||||||
|
nr_dosar: '',
|
||||||
|
termen: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = ref(emptyForm())
|
const form = ref(emptyForm())
|
||||||
@@ -181,8 +264,7 @@ const idSucursala = computed(() => selectedFirma.value?.id_mama != null ? form.v
|
|||||||
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 &&
|
||||||
form.value.id_masiniclient !== null &&
|
form.value.id_masiniclient !== null
|
||||||
form.value.solicitari.trim().length > 0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ─── Submit ────────────────────────────────────────────────────────────────
|
// ─── Submit ────────────────────────────────────────────────────────────────
|
||||||
@@ -192,12 +274,21 @@ async function submitComanda() {
|
|||||||
|
|
||||||
isSubmitting.value = true
|
isSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
|
const termen = form.value.termen
|
||||||
|
? new Date(form.value.termen).toISOString().split('T')[0]
|
||||||
|
: null
|
||||||
|
|
||||||
const { data } = await serviceAutoApi.creeazaComanda({
|
const { data } = await serviceAutoApi.creeazaComanda({
|
||||||
tip_id: form.value.tip_id,
|
tip_id: form.value.tip_id,
|
||||||
id_masiniclient: form.value.id_masiniclient,
|
id_masiniclient: form.value.id_masiniclient,
|
||||||
solicitari: form.value.solicitari.trim(),
|
|
||||||
id_firma: form.value.id_firma,
|
id_firma: form.value.id_firma,
|
||||||
id_sucursala: idSucursala.value,
|
id_sucursala: idSucursala.value,
|
||||||
|
observatii: form.value.observatii.trim() || '',
|
||||||
|
defectiuni: form.value.defectiuni.trim() || null,
|
||||||
|
km_int: form.value.km_int ?? 0,
|
||||||
|
ore_functionare: form.value.ore_functionare ?? 0,
|
||||||
|
nr_dosar: form.value.nr_dosar.trim() || '',
|
||||||
|
termen,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
|
|||||||
Reference in New Issue
Block a user