diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..640da1f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run FastAPI", + "type": "python", + "request": "launch", + "module": "uvicorn", + "args": ["main:app", "--reload"], + "jinja": true + } + ] +} diff --git a/contants.py b/contants.py new file mode 100644 index 0000000..2a4eb16 --- /dev/null +++ b/contants.py @@ -0,0 +1,5 @@ +NOME_PROGETTO = "ManganelliOrdiniAPI" +VERSIONE = "1.0.0" +DEFAULT_CODICE_CLIENTE = "000000" +MESSAGGIO_SUCCESSO = "Operazione completata con successo" +API_SECRET_KEY = "Z9KPT3QD2MAVNX81HJRY" \ No newline at end of file diff --git a/database.py b/database.py new file mode 100644 index 0000000..b2b608d --- /dev/null +++ b/database.py @@ -0,0 +1,16 @@ +import pyodbc +import os +from dotenv import load_dotenv + +load_dotenv() + +conn_str = ( + f"DRIVER={{ODBC Driver 17 for SQL Server}};" + f"SERVER={os.getenv('MSSQL_SERVER')};" + f"DATABASE={os.getenv('MSSQL_DATABASE')};" + f"UID={os.getenv('MSSQL_USERNAME')};" + f"PWD={os.getenv('MSSQL_PASSWORD')}" +) + +def get_connection(): + return pyodbc.connect(conn_str) diff --git a/main.py b/main.py new file mode 100644 index 0000000..12e2909 --- /dev/null +++ b/main.py @@ -0,0 +1,189 @@ +import random +import string +import pyodbc +from fastapi import FastAPI, HTTPException, Depends, Query +from typing import List +from database import get_connection +from models import ( + Cliente, SedeConsegna, Articolo, CodiceRicerca, + MetodoPagamento, DisponibilitaArticolo, + OrdineTestata, OrdineRiga,Magazzini +) +from contants import NOME_PROGETTO, MESSAGGIO_SUCCESSO,API_SECRET_KEY + +app = FastAPI() + +def genera_codice_random(lunghezza: int = 50) -> str: + caratteri = string.ascii_letters + string.digits # a-z A-Z 0-9 + return ''.join(random.choices(caratteri, k=lunghezza)) + +def genera_nuovo_codice_cliente(): + conn = None + try: + conn = get_connection() + cursor = conn.cursor() + cursor.execute("SELECT TOP 1 ANCODICE FROM MANGACONTI WHERE ANCODICE LIKE 'IK%' ORDER BY ANCODICE DESC") + row = cursor.fetchone() + if row: + ultimo_codice = row[0].strip() # es. "IK00000001" + numero = int(ultimo_codice[2:]) # parte numerica dopo "IK" + nuovo_numero = numero + 1 + else: + nuovo_numero = 1 + + nuovo_codice = f"IK{nuovo_numero:08d}" # 8 cifre numeriche con zeri davanti + nuovo_codice = nuovo_codice.ljust(15) # aggiunge spazi fino a 15 caratteri totali + return nuovo_codice + + except pyodbc.Error as e: + # Puoi loggare o rilanciare l'eccezione, o gestirla come vuoi + raise Exception(f"Errore DB nel generare codice cliente: {str(e)}") + + finally: + if conn: + conn.close() + +def verifica_secret_key(secret: str = Query(..., description="Chiave di accesso")): + if secret != API_SECRET_KEY: + raise HTTPException(status_code=403, detail="Accesso negato: chiave non valida") + +@app.get("/clienti", response_model=List[Cliente]) +def get_clienti(secret: str = Depends(verifica_secret_key)): + print(NOME_PROGETTO) + conn = get_connection() + cursor = conn.cursor() + cursor.execute("SELECT ANTIPCON, ANCODICE, ANDESCRI, ANPARIVA, ANCODFIS,ANINDIRI FROM MANGACONTI") + rows = cursor.fetchall() + return [Cliente(antipcon=r[0],ancodice=r[1], andescri=r[2], anpariva=r[3], ancodfis=r[4],anindiri=r[5]) for r in rows] + + +@app.get("/sedi-consegna", response_model=List[SedeConsegna]) +def get_sedi_consegna(codice_cliente: str,secret: str = Depends(verifica_secret_key)): + conn = get_connection() + cursor = conn.cursor() + cursor.execute("SELECT ddtipcon, ddcodice, ddcoddes,ddnomdes,ddindiri,dd___cap,ddlocali,ddprovin,ddcodnaz FROM MANGADES_DIVE WHERE ddtipcon='C' AND ddcodice = ?", codice_cliente) + rows = cursor.fetchall() + return [SedeConsegna(ddtipcon=r[0], ddcodice=r[1], ddcoddes=r[2],ddnomdes=r[3],ddindiri=r[4],dd___cap=r[5],ddlocali=r[6],ddprovin=r[7],ddcodnaz=r[8]) for r in rows] + + +@app.get("/articoli", response_model=List[Articolo]) +def get_articoli(secret: str = Depends(verifica_secret_key)): + conn = get_connection() + cursor = conn.cursor() + cursor.execute("select ARCODART, ARDESART,ARDESSUP,ARUNMIS1 from MANGAART_ICOL where ardtobso is null") + rows = cursor.fetchall() + return [Articolo(arcodart=r[0], ardesart=r[1], ardessup=r[2], arunmis1=r[3]) for r in rows] + + +@app.get("/codici-ricerca", response_model=List[CodiceRicerca]) +def get_codici_ricerca(secret: str = Depends(verifica_secret_key)): + conn = get_connection() + cursor = conn.cursor() + cursor.execute("select CACODICE, CADESART,CADESSUP, CACODART from MANGAKEY_ARTI where CADTOBSO is null") + rows = cursor.fetchall() + return [CodiceRicerca(cacodice=r[0], cadesart=r[1], cadessup=r[2], cacodart=r[3]) for r in rows] + + +@app.get("/metodi-pagamento", response_model=List[MetodoPagamento]) +def get_metodi_pagamento(secret: str = Depends(verifica_secret_key)): + conn = get_connection() + cursor = conn.cursor() + cursor.execute("select PACODICE, PADESCRI from MANGAPAG_AMEN where PADTOBSO is null") + rows = cursor.fetchall() + return [MetodoPagamento(pacodice=r[0], padescri=r[1]) for r in rows] + +@app.get("/magazzini", response_model=List[Magazzini]) +def get_magazzini(secret: str = Depends(verifica_secret_key)): + conn = get_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM MANGA_IKTOME_MAGAZ") + rows = cursor.fetchall() + return [Magazzini(mgcodmag=r[0], mgdesmag=r[1]) for r in rows] + +@app.get("/disponibilita", response_model=DisponibilitaArticolo) +def get_disponibilita(codice_articolo: str,secret: str = Depends(verifica_secret_key)): + conn = get_connection() + cursor = conn.cursor() + cursor.execute("select SLQTAPER from MANGASALDIART WHERE SLCODART = ?", codice_articolo) + row = cursor.fetchone() + if row: + return DisponibilitaArticolo(slcodart=codice_articolo, slqtaper=row[0]) + raise HTTPException(status_code=404, detail="Articolo non trovato") + +@app.post("/ordini") +def crea_ordine(ordine: OrdineTestata): + try: + conn = get_connection() + cursor = conn.cursor() + + # Se manca codice_cliente e ho dati cliente, creo nuovo cliente + if not ordine.codice_cliente and ordine.cliente: + codice_cliente = genera_nuovo_codice_cliente() # metodo che genera codice tipo "IK00000001 " + cursor.execute(""" + INSERT INTO MANGACONTI (ANTIPCON, ANCODICE, ANDESCRI, ANPARIVA, ANCODFIS, ANINDIRI) + VALUES (?, ?, ?, ?, ?, ?) + """, ( + "C", + codice_cliente, + ordine.cliente.andescri, + ordine.cliente.anpariva, + ordine.cliente.ancodfis, + ordine.cliente.anindiri + )) + conn.commit() + ordine.codice_cliente = codice_cliente + + if not ordine.codice_cliente: + raise HTTPException(status_code=400, detail="Codice cliente mancante e dati cliente non forniti") + + # Genera codice ordine + seriale_ordine = genera_codice_random() + ordine.orserial = seriale_ordine + + cursor.execute(""" + INSERT INTO MANGAZORDWEBM (orserial, or_stato, ortipdoc, ornumdoc, oralfdoc, ortipcon, orcodcon, orcodage, orcodpag, ortotord, ordatdoc) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + ordine.orserial, + ordine.or_stato, + ordine.ortipdoc, + ordine.ornumdoc, + ordine.oralfdoc, + ordine.ortipcon, + ordine.codice_cliente, + ordine.orcodage, + ordine.orcodpag, + ordine.ortotord, + ordine.ordatdoc + )) + conn.commit() + cursor.execute("SELECT SCOPE_IDENTITY()") + id_ordine = cursor.fetchone()[0] + + # Inserisce righe ordine + for riga in ordine.righe: + cursor.execute(""" + INSERT INTO MANGAZORDWEBD (orserial, cprownum, ordesart, ordessup, orqtamov, orcodart,orcodice) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, ( + ordine.orserial, + riga.cprownum, + riga.ordesart, + riga.ordessup, + riga.orqtamov, + riga.orcodart, + riga.orcodice + )) + conn.commit() + + return {"orserial": ordine.orserial, "messaggio": "Ordine creato con successo"} + + except pyodbc.Error as e: + conn.rollback() + raise HTTPException(status_code=500, detail=f"Errore DB: {str(e)}") + + except Exception as e: + raise HTTPException(status_code=500, detail=f"Errore generico: {str(e)}") + + finally: + conn.close() \ No newline at end of file diff --git a/models.py b/models.py new file mode 100644 index 0000000..a483475 --- /dev/null +++ b/models.py @@ -0,0 +1,73 @@ +from decimal import Decimal +from datetime import datetime +from pydantic import BaseModel,Field +from typing import List, Optional + +class Cliente(BaseModel): + antipcon: str= Field(..., description="Chiave primaria") + ancodice: str= Field(..., description="Chiave primaria") + andescri: Optional[str] = Field(None, description="Ragione sociale") + anpariva: Optional[str] = Field(None, description="Partita iva") + ancodfis: Optional[str] = Field(None, description="codice Fiscale") + anindiri: Optional[str] = Field(None, description="Indirizzo") + +class SedeConsegna(BaseModel): + ddtipcon: str= Field(description="Chiave primaria") + ddcodice: str= Field(description="Chiave primaria") + ddcoddes: str= Field(description="Chiave primaria") + ddnomdes: Optional[str] = Field(None, description="Nome della sede di destinazione") + ddindiri: Optional[str] = Field(None, description="Indirizzo della sede di destinazione") + dd___cap: Optional[str] = Field(None, description="CAP della sede di destinazione") + ddlocali: Optional[str] = Field(None, description="località della sede di destinazione") + ddprovin: Optional[str] = Field(None, description="Provincia della sede di destinazione") + ddcodnaz: Optional[str] = Field(None, description="nazione della sede di destinazione") + +class Articolo(BaseModel): + arcodart: str= Field(..., description="Chiave primaria") + ardesart: Optional[str]= Field(None, description="Descrizione articolo") + ardessup: Optional[str]= Field(None, description="Descrizione supplementare") + arunmis1: Optional[str]= Field(None, description="Unità di misura") + +class CodiceRicerca(BaseModel): + cacodice: str = Field(..., description="Chiave primaria") + cadesart: Optional[str]= Field(None, description="Descrizione codice ricerca") + cacodart: Optional[str]= Field(None, description="articolo principale") + cadessup: Optional[str]= Field(None, description="Descrizione supplementare codice ricerca") + +class MetodoPagamento(BaseModel): + pacodice: str = Field(..., description="Chiave primaria") + padescri: Optional[str]= Field(None, description="Descrizione metodo pagamento") + +class Magazzini(BaseModel): + mgcodmag: str = Field(..., description="Chiave primaria") + mgdesmag: Optional[str]= Field(None, description="Descrizione magazzino") + +class DisponibilitaArticolo(BaseModel): + slqtaper: int = Field(..., description="qta disponibile") + slcodart: str = Field(..., description="Chiave primaria") + +class OrdineRiga(BaseModel): + orserial: str = Field(..., description="Chiave primaria") + cprownum: int = Field(..., description="num riga Chiave primaria") + orcodart: str = Field(..., description="codice articolo") + ordesart: str = Field(..., description="descrizione articolo") + ordessup: str = Field(..., description="descrizione suppl. articolo") + orqtamov: int = Field(..., description="quantità") + orcodice: str = Field(..., description="chiave di ricerca articolo") + + +class OrdineTestata(BaseModel): + orserial: str = Field(..., description="Chiave primaria") + or_stato: int = Field(..., description="Stato ordine def: 1") + ortipdoc: str = Field(..., description="Tipo documento def: OR=ordine") + ornumdoc: Optional[Decimal]= Field(None, description="Numero documento") + oralfdoc: Optional[str]= Field(None, description="Sezionale documento ordine") + ortipcon: str = Field(..., description="Tipo anagrafica Clienti=C") + orcodcon: str = Field(..., description="codice anagrafica cliente") + orcodage: Optional[str]= Field(None, description="codice agente") + orcodpag: Optional[str]= Field(None, description="codice pagamento") + ortotord: Optional[Decimal]= Field(None, description="Totale ordine") + ordatdoc: datetime = Field(..., description="data documento") + righe: List[OrdineRiga] + cliente: Optional[Cliente] = None + codice_cliente: Optional[str]= Field(None, description="Codice cliente nel caso l'anagrafica esista") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bb0fce2 Binary files /dev/null and b/requirements.txt differ