feat(back-end): new db for custom quote requests
This commit is contained in:
47
GEMINI.md
47
GEMINI.md
@@ -4,39 +4,42 @@ Questo file serve a dare contesto all'AI (Antigravity/Gemini) sulla struttura e
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
**Nome**: Print Calculator
|
**Nome**: Print Calculator
|
||||||
**Scopo**: Calcolare costi e tempi di stampa 3D da file STL.
|
**Scopo**: Calcolare costi e tempi di stampa 3D da file STL in modo preciso tramite slicing reale.
|
||||||
**Stack**:
|
**Stack**:
|
||||||
- **Backend**: Python (FastAPI), libreria `trimesh` per analisi geometrica.
|
- **Backend**: Java 21 (Spring Boot 3.4), PostgreSQL, Flyway.
|
||||||
- **Frontend**: Angular 19 (TypeScript).
|
- **Frontend**: Angular 19 (TypeScript), Angular Material, Three.js per visualizzazione 3D.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Backend (`/backend`)
|
### Backend (`/backend`)
|
||||||
- **`main.py`**: Entrypoint dell'applicazione FastAPI.
|
- **`BackendApplication.java`**: Entrypoint dell'applicazione Spring Boot.
|
||||||
- Definisce l'API `POST /calculate/stl`.
|
- **`controller/`**: Espone le API REST per l'upload e il calcolo dei preventivi.
|
||||||
- Gestisce l'upload del file, invoca lo slicer e restituisce il preventivo.
|
- **`service/SlicerService.java`**: Wrappa l'eseguibile di **OrcaSlicer** per effettuare lo slicing reale del modello.
|
||||||
- Configura CORS per permettere chiamate dal frontend.
|
- Gestisce i profili di stampa (Macchina, Processo, Filamento) caricati da file JSON.
|
||||||
- **`slicer.py`**: Wrappa l'eseguibile di **OrcaSlicer** per effettuare lo slicing reale del modello.
|
- Crea configurazioni on-the-fly e invoca OrcaSlicer in modalità headless.
|
||||||
- Gestisce i profili di stampa (Macchina, Processo, Filamento).
|
- **`service/GCodeParser.java`**: Analizza il G-Code generato per estrarre tempo di stampa e peso del materiale dai metadati del file.
|
||||||
- Crea configurazioni on-the-fly per supportare mesh di grandi dimensioni.
|
- **`service/QuoteCalculator.java`**: Calcola il prezzo finale basandosi su politiche di prezzo salvate nel database.
|
||||||
- **`calculator.py`**: Analizza il G-Code generato.
|
- Gestisce costi macchina a scaglioni (tiered pricing).
|
||||||
- `GCodeParser`: Estrae tempo di stampa e materiale usato dai metadati del G-Code.
|
- Calcola costi energetici basati sulla potenza della stampante e costo del kWh.
|
||||||
- `QuoteCalculator`: Applica i costi (orari, energia, materiale) per generare il prezzo finale.
|
- Applica markup percentuali e fee fissi per job.
|
||||||
|
|
||||||
### Frontend (`/frontend`)
|
### Frontend (`/frontend`)
|
||||||
- Applicazione Angular standard.
|
- Applicazione Angular 19 con architettura modulare (core, features, shared).
|
||||||
- Usa Angular Material.
|
- **Three.js**: Utilizzato per il rendering dei file STL caricati dall'utente.
|
||||||
- Service per upload STL e visualizzazione preventivo.
|
- **Angular Material**: Per l'interfaccia utente.
|
||||||
|
- **ngx-translate**: Per il supporto multilingua.
|
||||||
|
|
||||||
## Key Concepts
|
## Key Concepts
|
||||||
- **Real Slicing**: Il backend esegue un vero slicing usando OrcaSlicer in modalità headless. Questo garantisce stime di tempo e materiale estremamente precise, identiche a quelle che si otterrebbero preparando il file per la stampa.
|
- **Real Slicing**: Il backend esegue un vero slicing usando OrcaSlicer. Questo garantisce stime di tempo e materiale estremamente precise.
|
||||||
- **G-Code Parsing**: Invece di stimare geometricamente, l'applicazione legge direttamene i commenti generati dallo slicer nel G-Code (es. `estimated printing time`, `filament used`).
|
- **Database-Driven Pricing**: A differenza di versioni precedenti, il calcolo del preventivo è ora guidato da entità DB (`PricingPolicy`, `PrinterMachine`, `FilamentVariant`).
|
||||||
|
- **G-Code Metadata**: L'applicazione legge direttamene i commenti generati dallo slicer nel G-Code (es. `; estimated printing time`, `; filament used [g]`).
|
||||||
|
|
||||||
## Development Notes
|
## Development Notes
|
||||||
- Per eseguire il backend serve `uvicorn`.
|
- **Backend**: Richiede JDK 21. Si avvia con `./gradlew bootRun`.
|
||||||
- Il frontend richiede `npm install` al primo avvio.
|
- **Database**: Richiede PostgreSQL. Le migrazioni sono gestite da Flyway.
|
||||||
- Le configurazioni di stampa (layer height, wall thickness, infill) sono attualmente hardcoded o con valori di default nel backend, ma potrebbero essere esposte come parametri API in futuro.
|
- **Frontend**: Richiede Node.js 22. Si avvia con `npm start`.
|
||||||
|
- **OrcaSlicer**: Deve essere installato sul sistema e il percorso configurato in `application.properties` o tramite variabile d'ambiente `SLICER_PATH`.
|
||||||
|
|
||||||
## AI Agent Rules
|
## AI Agent Rules
|
||||||
- **No Inline Code**: Tutti i componenti Angular DEVONO usare file separati per HTML (`templateUrl`) e SCSS (`styleUrl`). È vietato usare `template` o `styles` inline nel decoratore `@Component`.
|
- **No Inline Code**: Tutti i componenti Angular DEVONO usare file separati per HTML (`templateUrl`) e SCSS (`styleUrl`). È vietato usare `template` o `styles` inline nel decoratore `@Component`.
|
||||||
|
- **Spring Boot Conventions**: Seguire i pattern standard di Spring Boot (Service-Repository-Controller).
|
||||||
|
|||||||
93
README.md
93
README.md
@@ -1,70 +1,67 @@
|
|||||||
# Print Calculator (OrcaSlicer Edition)
|
# Print Calculator (OrcaSlicer Edition)
|
||||||
|
|
||||||
Un'applicazione Full Stack (Angular + Python/FastAPI) per calcolare preventivi di stampa 3D precisi utilizzando **OrcaSlicer** in modalità headless.
|
Un'applicazione Full Stack (Angular + Spring Boot) per calcolare preventivi di stampa 3D precisi utilizzando **OrcaSlicer** in modalità headless.
|
||||||
|
|
||||||
## Funzionalità
|
## Funzionalità
|
||||||
|
|
||||||
* **Slicing Reale**: Usa il motore di OrcaSlicer per stimare tempo e materiale, non semplici approssimazioni geometriche.
|
* **Slicing Reale**: Usa il motore di OrcaSlicer per stimare tempo e materiale, garantendo la massima precisione.
|
||||||
* **Preventivazione Completa**: Calcola costo materiale, ammortamento macchina, energia e ricarico.
|
* **Preventivazione Database-Driven**: Calcolo basato su politiche di prezzo configurabili nel database (costo materiale, ammortamento macchina a scaglioni, energia e markup).
|
||||||
* **Configurabile**: Prezzi e parametri macchina modificabili via variabili d'ambiente.
|
* **Visualizzazione 3D**: Anteprima del file STL caricato tramite Three.js.
|
||||||
* **Docker Ready**: Tutto containerizzato per un facile deployment.
|
* **Multi-Profilo**: Supporto per diverse stampanti, materiali e profili di processo.
|
||||||
|
|
||||||
|
## Stack Tecnologico
|
||||||
|
|
||||||
|
- **Backend**: Java 21, Spring Boot 3.4, PostgreSQL, Flyway.
|
||||||
|
- **Frontend**: Angular 19, Angular Material, Three.js.
|
||||||
|
- **Slicer**: OrcaSlicer (invocato via CLI).
|
||||||
|
|
||||||
## Prerequisiti
|
## Prerequisiti
|
||||||
|
|
||||||
* Docker Desktop & Docker Compose installati.
|
* **Java 21** installato.
|
||||||
|
* **Node.js 22** e **npm** installati.
|
||||||
|
* **PostgreSQL** attivo.
|
||||||
|
* **OrcaSlicer** installato sul sistema.
|
||||||
|
|
||||||
## Avvio Rapido
|
## Avvio Rapido
|
||||||
|
|
||||||
1. Clona il repository.
|
### 1. Database
|
||||||
2. Esegui lo script di avvio o docker-compose:
|
Crea un database PostgreSQL chiamato `printcalc`. Le tabelle verranno create automaticamente al primo avvio tramite Flyway.
|
||||||
```bash
|
|
||||||
docker-compose up --build
|
|
||||||
```
|
|
||||||
*Nota: La prima build impiegherà alcuni minuti per scaricare OrcaSlicer (~200MB) e compilare il Frontend.*
|
|
||||||
|
|
||||||
3. Accedi all'applicazione:
|
### 2. Backend
|
||||||
* **Frontend**: [http://localhost](http://localhost)
|
Configura il percorso di OrcaSlicer in `backend/src/main/resources/application.properties` o tramite la variabile d'ambiente `SLICER_PATH`.
|
||||||
* **API Docs**: [http://localhost:8000/docs](http://localhost:8000/docs)
|
|
||||||
|
|
||||||
## Configurazione Prezzi
|
|
||||||
|
|
||||||
Puoi modificare i prezzi nel file `docker-compose.yml` (sezione `environment` del servizio backend):
|
|
||||||
|
|
||||||
* `FILAMENT_COST_PER_KG`: Costo filamento al kg (es. 25.0).
|
|
||||||
* `MACHINE_COST_PER_HOUR`: Costo orario macchina (ammortamento/manutenzione).
|
|
||||||
* `ENERGY_COST_PER_KWH`: Costo energia elettrica.
|
|
||||||
* `MARKUP_PERCENT`: Margine di profitto percentuale (es. 20 = +20%).
|
|
||||||
|
|
||||||
## Struttura del Progetto
|
|
||||||
|
|
||||||
* `/backend`: API Python FastAPI. Include Dockerfile che scarica OrcaSlicer AppImage.
|
|
||||||
* `/frontend`: Applicazione Angular 19+ con Material Design.
|
|
||||||
* `/backend/profiles`: Contiene i profili di slicing (.ini). Attualmente configurato per una stima generica simil-Bambu Lab A1.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Errore Download OrcaSlicer
|
|
||||||
Se la build del backend fallisce durante il download di `OrcaSlicer.AppImage`, verifica la tua connessione internet o aggiorna l'URL nel `backend/Dockerfile`.
|
|
||||||
|
|
||||||
### Slicing Fallito (Costo 0 o Errore)
|
|
||||||
Se l'API ritorna errore o valori nulli:
|
|
||||||
1. Controlla che il file STL sia valido (manifold).
|
|
||||||
2. Controlla i log del backend: `docker logs print-calculator-backend`.
|
|
||||||
|
|
||||||
## Sviluppo Locale (Senza Docker)
|
|
||||||
|
|
||||||
**Backend**:
|
|
||||||
Richiede Linux (o WSL2) per eseguire l'AppImage di OrcaSlicer.
|
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
pip install -r requirements.txt
|
./gradlew bootRun
|
||||||
# Assicurati di avere OrcaSlicer installato e nel PATH o aggiorna SLICER_PATH in slicer.py
|
|
||||||
uvicorn main:app --reload
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Frontend**:
|
### 3. Frontend
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Accedi a [http://localhost:4200](http://localhost:4200).
|
||||||
|
|
||||||
|
## Configurazione Prezzi
|
||||||
|
|
||||||
|
I prezzi non sono più gestiti tramite variabili d'ambiente fisse ma tramite tabelle nel database:
|
||||||
|
- `pricing_policy`: Definisce markup, fee fissi e costi elettrici.
|
||||||
|
- `pricing_policy_machine_hour_tier`: Definisce i costi orari delle macchine in base alla durata della stampa.
|
||||||
|
- `printer_machine`: Anagrafica stampanti e consumi energetici.
|
||||||
|
- `filament_material_type` / `filament_variant`: Listino prezzi materiali.
|
||||||
|
|
||||||
|
## Struttura del Progetto
|
||||||
|
|
||||||
|
* `/backend`: API Spring Boot.
|
||||||
|
* `/frontend`: Applicazione Angular.
|
||||||
|
* `/backend/profiles`: Contiene i file di configurazione per OrcaSlicer.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Percorso OrcaSlicer
|
||||||
|
Assicurati che `slicer.path` punti al binario corretto. Su macOS è solitamente `/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer`. Su Linux è il percorso all'AppImage (estratta o meno).
|
||||||
|
|
||||||
|
### Database connection
|
||||||
|
Verifica le credenziali in `application.properties`. Se usi Docker, puoi passare `DB_URL`, `DB_USERNAME` e `DB_PASSWORD` come variabili d'ambiente.
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "custom_quote_requests", indexes = {@Index(name = "ix_custom_quote_requests_status",
|
||||||
|
columnList = "status")})
|
||||||
|
public class CustomQuoteRequest {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "request_id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(name = "request_type", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String requestType;
|
||||||
|
|
||||||
|
@Column(name = "customer_type", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String customerType;
|
||||||
|
|
||||||
|
@Column(name = "email", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(name = "phone", length = Integer.MAX_VALUE)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Column(name = "name", length = Integer.MAX_VALUE)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "company_name", length = Integer.MAX_VALUE)
|
||||||
|
private String companyName;
|
||||||
|
|
||||||
|
@Column(name = "contact_person", length = Integer.MAX_VALUE)
|
||||||
|
private String contactPerson;
|
||||||
|
|
||||||
|
@Column(name = "message", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private OffsetDateTime updatedAt;
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestType() {
|
||||||
|
return requestType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestType(String requestType) {
|
||||||
|
this.requestType = requestType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomerType() {
|
||||||
|
return customerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerType(String customerType) {
|
||||||
|
this.customerType = customerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCompanyName() {
|
||||||
|
return companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompanyName(String companyName) {
|
||||||
|
this.companyName = companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactPerson() {
|
||||||
|
return contactPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactPerson(String contactPerson) {
|
||||||
|
this.contactPerson = contactPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "custom_quote_request_attachments", indexes = {@Index(name = "ix_custom_quote_attachments_request",
|
||||||
|
columnList = "request_id")})
|
||||||
|
public class CustomQuoteRequestAttachment {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "attachment_id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
@JoinColumn(name = "request_id", nullable = false)
|
||||||
|
private CustomQuoteRequest request;
|
||||||
|
|
||||||
|
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String originalFilename;
|
||||||
|
|
||||||
|
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String storedRelativePath;
|
||||||
|
|
||||||
|
@Column(name = "stored_filename", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String storedFilename;
|
||||||
|
|
||||||
|
@Column(name = "file_size_bytes")
|
||||||
|
private Long fileSizeBytes;
|
||||||
|
|
||||||
|
@Column(name = "mime_type", length = Integer.MAX_VALUE)
|
||||||
|
private String mimeType;
|
||||||
|
|
||||||
|
@Column(name = "sha256_hex", length = Integer.MAX_VALUE)
|
||||||
|
private String sha256Hex;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomQuoteRequest getRequest() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequest(CustomQuoteRequest request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOriginalFilename() {
|
||||||
|
return originalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOriginalFilename(String originalFilename) {
|
||||||
|
this.originalFilename = originalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStoredRelativePath() {
|
||||||
|
return storedRelativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoredRelativePath(String storedRelativePath) {
|
||||||
|
this.storedRelativePath = storedRelativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStoredFilename() {
|
||||||
|
return storedFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoredFilename(String storedFilename) {
|
||||||
|
this.storedFilename = storedFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getFileSizeBytes() {
|
||||||
|
return fileSizeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileSizeBytes(Long fileSizeBytes) {
|
||||||
|
this.fileSizeBytes = fileSizeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMimeType(String mimeType) {
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSha256Hex() {
|
||||||
|
return sha256Hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSha256Hex(String sha256Hex) {
|
||||||
|
this.sha256Hex = sha256Hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
126
backend/src/main/java/com/printcalculator/entity/Customer.java
Normal file
126
backend/src/main/java/com/printcalculator/entity/Customer.java
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "customers")
|
||||||
|
public class Customer {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "customer_id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(name = "customer_type", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String customerType;
|
||||||
|
|
||||||
|
@Column(name = "email", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(name = "phone", length = Integer.MAX_VALUE)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Column(name = "first_name", length = Integer.MAX_VALUE)
|
||||||
|
private String firstName;
|
||||||
|
|
||||||
|
@Column(name = "last_name", length = Integer.MAX_VALUE)
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
@Column(name = "company_name", length = Integer.MAX_VALUE)
|
||||||
|
private String companyName;
|
||||||
|
|
||||||
|
@Column(name = "contact_person", length = Integer.MAX_VALUE)
|
||||||
|
private String contactPerson;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private OffsetDateTime updatedAt;
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomerType() {
|
||||||
|
return customerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerType(String customerType) {
|
||||||
|
this.customerType = customerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCompanyName() {
|
||||||
|
return companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompanyName(String companyName) {
|
||||||
|
this.companyName = companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactPerson() {
|
||||||
|
return contactPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactPerson(String contactPerson) {
|
||||||
|
this.contactPerson = contactPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
413
backend/src/main/java/com/printcalculator/entity/Order.java
Normal file
413
backend/src/main/java/com/printcalculator/entity/Order.java
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "orders", indexes = {@Index(name = "ix_orders_status",
|
||||||
|
columnList = "status")})
|
||||||
|
public class Order {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "order_id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "source_quote_session_id")
|
||||||
|
private QuoteSession sourceQuoteSession;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "customer_id")
|
||||||
|
private Customer customer;
|
||||||
|
|
||||||
|
@Column(name = "customer_email", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String customerEmail;
|
||||||
|
|
||||||
|
@Column(name = "customer_phone", length = Integer.MAX_VALUE)
|
||||||
|
private String customerPhone;
|
||||||
|
|
||||||
|
@Column(name = "billing_customer_type", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String billingCustomerType;
|
||||||
|
|
||||||
|
@Column(name = "billing_first_name", length = Integer.MAX_VALUE)
|
||||||
|
private String billingFirstName;
|
||||||
|
|
||||||
|
@Column(name = "billing_last_name", length = Integer.MAX_VALUE)
|
||||||
|
private String billingLastName;
|
||||||
|
|
||||||
|
@Column(name = "billing_company_name", length = Integer.MAX_VALUE)
|
||||||
|
private String billingCompanyName;
|
||||||
|
|
||||||
|
@Column(name = "billing_contact_person", length = Integer.MAX_VALUE)
|
||||||
|
private String billingContactPerson;
|
||||||
|
|
||||||
|
@Column(name = "billing_address_line1", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String billingAddressLine1;
|
||||||
|
|
||||||
|
@Column(name = "billing_address_line2", length = Integer.MAX_VALUE)
|
||||||
|
private String billingAddressLine2;
|
||||||
|
|
||||||
|
@Column(name = "billing_zip", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String billingZip;
|
||||||
|
|
||||||
|
@Column(name = "billing_city", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String billingCity;
|
||||||
|
|
||||||
|
@ColumnDefault("'CH'")
|
||||||
|
@Column(name = "billing_country_code", nullable = false, length = 2)
|
||||||
|
private String billingCountryCode;
|
||||||
|
|
||||||
|
@ColumnDefault("true")
|
||||||
|
@Column(name = "shipping_same_as_billing", nullable = false)
|
||||||
|
private Boolean shippingSameAsBilling;
|
||||||
|
|
||||||
|
@Column(name = "shipping_first_name", length = Integer.MAX_VALUE)
|
||||||
|
private String shippingFirstName;
|
||||||
|
|
||||||
|
@Column(name = "shipping_last_name", length = Integer.MAX_VALUE)
|
||||||
|
private String shippingLastName;
|
||||||
|
|
||||||
|
@Column(name = "shipping_company_name", length = Integer.MAX_VALUE)
|
||||||
|
private String shippingCompanyName;
|
||||||
|
|
||||||
|
@Column(name = "shipping_contact_person", length = Integer.MAX_VALUE)
|
||||||
|
private String shippingContactPerson;
|
||||||
|
|
||||||
|
@Column(name = "shipping_address_line1", length = Integer.MAX_VALUE)
|
||||||
|
private String shippingAddressLine1;
|
||||||
|
|
||||||
|
@Column(name = "shipping_address_line2", length = Integer.MAX_VALUE)
|
||||||
|
private String shippingAddressLine2;
|
||||||
|
|
||||||
|
@Column(name = "shipping_zip", length = Integer.MAX_VALUE)
|
||||||
|
private String shippingZip;
|
||||||
|
|
||||||
|
@Column(name = "shipping_city", length = Integer.MAX_VALUE)
|
||||||
|
private String shippingCity;
|
||||||
|
|
||||||
|
@Column(name = "shipping_country_code", length = 2)
|
||||||
|
private String shippingCountryCode;
|
||||||
|
|
||||||
|
@ColumnDefault("'CHF'")
|
||||||
|
@Column(name = "currency", nullable = false, length = 3)
|
||||||
|
private String currency;
|
||||||
|
|
||||||
|
@ColumnDefault("0.00")
|
||||||
|
@Column(name = "setup_cost_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal setupCostChf;
|
||||||
|
|
||||||
|
@ColumnDefault("0.00")
|
||||||
|
@Column(name = "shipping_cost_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal shippingCostChf;
|
||||||
|
|
||||||
|
@ColumnDefault("0.00")
|
||||||
|
@Column(name = "discount_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal discountChf;
|
||||||
|
|
||||||
|
@ColumnDefault("0.00")
|
||||||
|
@Column(name = "subtotal_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal subtotalChf;
|
||||||
|
|
||||||
|
@ColumnDefault("0.00")
|
||||||
|
@Column(name = "total_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal totalChf;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private OffsetDateTime updatedAt;
|
||||||
|
|
||||||
|
@Column(name = "paid_at")
|
||||||
|
private OffsetDateTime paidAt;
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuoteSession getSourceQuoteSession() {
|
||||||
|
return sourceQuoteSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceQuoteSession(QuoteSession sourceQuoteSession) {
|
||||||
|
this.sourceQuoteSession = sourceQuoteSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer getCustomer() {
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomer(Customer customer) {
|
||||||
|
this.customer = customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomerEmail() {
|
||||||
|
return customerEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerEmail(String customerEmail) {
|
||||||
|
this.customerEmail = customerEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomerPhone() {
|
||||||
|
return customerPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerPhone(String customerPhone) {
|
||||||
|
this.customerPhone = customerPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingCustomerType() {
|
||||||
|
return billingCustomerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingCustomerType(String billingCustomerType) {
|
||||||
|
this.billingCustomerType = billingCustomerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingFirstName() {
|
||||||
|
return billingFirstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingFirstName(String billingFirstName) {
|
||||||
|
this.billingFirstName = billingFirstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingLastName() {
|
||||||
|
return billingLastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingLastName(String billingLastName) {
|
||||||
|
this.billingLastName = billingLastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingCompanyName() {
|
||||||
|
return billingCompanyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingCompanyName(String billingCompanyName) {
|
||||||
|
this.billingCompanyName = billingCompanyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingContactPerson() {
|
||||||
|
return billingContactPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingContactPerson(String billingContactPerson) {
|
||||||
|
this.billingContactPerson = billingContactPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingAddressLine1() {
|
||||||
|
return billingAddressLine1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingAddressLine1(String billingAddressLine1) {
|
||||||
|
this.billingAddressLine1 = billingAddressLine1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingAddressLine2() {
|
||||||
|
return billingAddressLine2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingAddressLine2(String billingAddressLine2) {
|
||||||
|
this.billingAddressLine2 = billingAddressLine2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingZip() {
|
||||||
|
return billingZip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingZip(String billingZip) {
|
||||||
|
this.billingZip = billingZip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingCity() {
|
||||||
|
return billingCity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingCity(String billingCity) {
|
||||||
|
this.billingCity = billingCity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingCountryCode() {
|
||||||
|
return billingCountryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingCountryCode(String billingCountryCode) {
|
||||||
|
this.billingCountryCode = billingCountryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getShippingSameAsBilling() {
|
||||||
|
return shippingSameAsBilling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingSameAsBilling(Boolean shippingSameAsBilling) {
|
||||||
|
this.shippingSameAsBilling = shippingSameAsBilling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingFirstName() {
|
||||||
|
return shippingFirstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingFirstName(String shippingFirstName) {
|
||||||
|
this.shippingFirstName = shippingFirstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingLastName() {
|
||||||
|
return shippingLastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingLastName(String shippingLastName) {
|
||||||
|
this.shippingLastName = shippingLastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingCompanyName() {
|
||||||
|
return shippingCompanyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingCompanyName(String shippingCompanyName) {
|
||||||
|
this.shippingCompanyName = shippingCompanyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingContactPerson() {
|
||||||
|
return shippingContactPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingContactPerson(String shippingContactPerson) {
|
||||||
|
this.shippingContactPerson = shippingContactPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingAddressLine1() {
|
||||||
|
return shippingAddressLine1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingAddressLine1(String shippingAddressLine1) {
|
||||||
|
this.shippingAddressLine1 = shippingAddressLine1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingAddressLine2() {
|
||||||
|
return shippingAddressLine2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingAddressLine2(String shippingAddressLine2) {
|
||||||
|
this.shippingAddressLine2 = shippingAddressLine2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingZip() {
|
||||||
|
return shippingZip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingZip(String shippingZip) {
|
||||||
|
this.shippingZip = shippingZip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingCity() {
|
||||||
|
return shippingCity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingCity(String shippingCity) {
|
||||||
|
this.shippingCity = shippingCity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShippingCountryCode() {
|
||||||
|
return shippingCountryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingCountryCode(String shippingCountryCode) {
|
||||||
|
this.shippingCountryCode = shippingCountryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrency(String currency) {
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getSetupCostChf() {
|
||||||
|
return setupCostChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSetupCostChf(BigDecimal setupCostChf) {
|
||||||
|
this.setupCostChf = setupCostChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getShippingCostChf() {
|
||||||
|
return shippingCostChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShippingCostChf(BigDecimal shippingCostChf) {
|
||||||
|
this.shippingCostChf = shippingCostChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getDiscountChf() {
|
||||||
|
return discountChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDiscountChf(BigDecimal discountChf) {
|
||||||
|
this.discountChf = discountChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getSubtotalChf() {
|
||||||
|
return subtotalChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubtotalChf(BigDecimal subtotalChf) {
|
||||||
|
this.subtotalChf = subtotalChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getTotalChf() {
|
||||||
|
return totalChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalChf(BigDecimal totalChf) {
|
||||||
|
this.totalChf = totalChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getPaidAt() {
|
||||||
|
return paidAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaidAt(OffsetDateTime paidAt) {
|
||||||
|
this.paidAt = paidAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
198
backend/src/main/java/com/printcalculator/entity/OrderItem.java
Normal file
198
backend/src/main/java/com/printcalculator/entity/OrderItem.java
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "order_items", indexes = {@Index(name = "ix_order_items_order",
|
||||||
|
columnList = "order_id")})
|
||||||
|
public class OrderItem {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "order_item_id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
@JoinColumn(name = "order_id", nullable = false)
|
||||||
|
private Order order;
|
||||||
|
|
||||||
|
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String originalFilename;
|
||||||
|
|
||||||
|
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String storedRelativePath;
|
||||||
|
|
||||||
|
@Column(name = "stored_filename", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String storedFilename;
|
||||||
|
|
||||||
|
@Column(name = "file_size_bytes")
|
||||||
|
private Long fileSizeBytes;
|
||||||
|
|
||||||
|
@Column(name = "mime_type", length = Integer.MAX_VALUE)
|
||||||
|
private String mimeType;
|
||||||
|
|
||||||
|
@Column(name = "sha256_hex", length = Integer.MAX_VALUE)
|
||||||
|
private String sha256Hex;
|
||||||
|
|
||||||
|
@Column(name = "material_code", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String materialCode;
|
||||||
|
|
||||||
|
@Column(name = "color_code", length = Integer.MAX_VALUE)
|
||||||
|
private String colorCode;
|
||||||
|
|
||||||
|
@ColumnDefault("1")
|
||||||
|
@Column(name = "quantity", nullable = false)
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
|
@Column(name = "print_time_seconds")
|
||||||
|
private Integer printTimeSeconds;
|
||||||
|
|
||||||
|
@Column(name = "material_grams", precision = 12, scale = 2)
|
||||||
|
private BigDecimal materialGrams;
|
||||||
|
|
||||||
|
@Column(name = "unit_price_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal unitPriceChf;
|
||||||
|
|
||||||
|
@Column(name = "line_total_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal lineTotalChf;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order getOrder() {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrder(Order order) {
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOriginalFilename() {
|
||||||
|
return originalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOriginalFilename(String originalFilename) {
|
||||||
|
this.originalFilename = originalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStoredRelativePath() {
|
||||||
|
return storedRelativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoredRelativePath(String storedRelativePath) {
|
||||||
|
this.storedRelativePath = storedRelativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStoredFilename() {
|
||||||
|
return storedFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoredFilename(String storedFilename) {
|
||||||
|
this.storedFilename = storedFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getFileSizeBytes() {
|
||||||
|
return fileSizeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileSizeBytes(Long fileSizeBytes) {
|
||||||
|
this.fileSizeBytes = fileSizeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMimeType(String mimeType) {
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSha256Hex() {
|
||||||
|
return sha256Hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSha256Hex(String sha256Hex) {
|
||||||
|
this.sha256Hex = sha256Hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMaterialCode() {
|
||||||
|
return materialCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaterialCode(String materialCode) {
|
||||||
|
this.materialCode = materialCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorCode() {
|
||||||
|
return colorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorCode(String colorCode) {
|
||||||
|
this.colorCode = colorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(Integer quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPrintTimeSeconds() {
|
||||||
|
return printTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrintTimeSeconds(Integer printTimeSeconds) {
|
||||||
|
this.printTimeSeconds = printTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaterialGrams() {
|
||||||
|
return materialGrams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaterialGrams(BigDecimal materialGrams) {
|
||||||
|
this.materialGrams = materialGrams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getUnitPriceChf() {
|
||||||
|
return unitPriceChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnitPriceChf(BigDecimal unitPriceChf) {
|
||||||
|
this.unitPriceChf = unitPriceChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getLineTotalChf() {
|
||||||
|
return lineTotalChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLineTotalChf(BigDecimal lineTotalChf) {
|
||||||
|
this.lineTotalChf = lineTotalChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
146
backend/src/main/java/com/printcalculator/entity/Payment.java
Normal file
146
backend/src/main/java/com/printcalculator/entity/Payment.java
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "payments", indexes = {
|
||||||
|
@Index(name = "ix_payments_order",
|
||||||
|
columnList = "order_id"),
|
||||||
|
@Index(name = "ix_payments_reference",
|
||||||
|
columnList = "payment_reference")})
|
||||||
|
public class Payment {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "payment_id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
@JoinColumn(name = "order_id", nullable = false)
|
||||||
|
private Order order;
|
||||||
|
|
||||||
|
@Column(name = "method", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String method;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@ColumnDefault("'CHF'")
|
||||||
|
@Column(name = "currency", nullable = false, length = 3)
|
||||||
|
private String currency;
|
||||||
|
|
||||||
|
@Column(name = "amount_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal amountChf;
|
||||||
|
|
||||||
|
@Column(name = "payment_reference", length = Integer.MAX_VALUE)
|
||||||
|
private String paymentReference;
|
||||||
|
|
||||||
|
@Column(name = "provider_transaction_id", length = Integer.MAX_VALUE)
|
||||||
|
private String providerTransactionId;
|
||||||
|
|
||||||
|
@Column(name = "qr_payload", length = Integer.MAX_VALUE)
|
||||||
|
private String qrPayload;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "initiated_at", nullable = false)
|
||||||
|
private OffsetDateTime initiatedAt;
|
||||||
|
|
||||||
|
@Column(name = "received_at")
|
||||||
|
private OffsetDateTime receivedAt;
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order getOrder() {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrder(Order order) {
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrency(String currency) {
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getAmountChf() {
|
||||||
|
return amountChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmountChf(BigDecimal amountChf) {
|
||||||
|
this.amountChf = amountChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPaymentReference() {
|
||||||
|
return paymentReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaymentReference(String paymentReference) {
|
||||||
|
this.paymentReference = paymentReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProviderTransactionId() {
|
||||||
|
return providerTransactionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProviderTransactionId(String providerTransactionId) {
|
||||||
|
this.providerTransactionId = providerTransactionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQrPayload() {
|
||||||
|
return qrPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQrPayload(String qrPayload) {
|
||||||
|
this.qrPayload = qrPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getInitiatedAt() {
|
||||||
|
return initiatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitiatedAt(OffsetDateTime initiatedAt) {
|
||||||
|
this.initiatedAt = initiatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getReceivedAt() {
|
||||||
|
return receivedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReceivedAt(OffsetDateTime receivedAt) {
|
||||||
|
this.receivedAt = receivedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "quote_line_items", indexes = {@Index(name = "ix_quote_line_items_session",
|
||||||
|
columnList = "quote_session_id")})
|
||||||
|
public class QuoteLineItem {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "quote_line_item_id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
@JoinColumn(name = "quote_session_id", nullable = false)
|
||||||
|
private QuoteSession quoteSession;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String originalFilename;
|
||||||
|
|
||||||
|
@ColumnDefault("1")
|
||||||
|
@Column(name = "quantity", nullable = false)
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
|
@Column(name = "color_code", length = Integer.MAX_VALUE)
|
||||||
|
private String colorCode;
|
||||||
|
|
||||||
|
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
|
||||||
|
private BigDecimal boundingBoxXMm;
|
||||||
|
|
||||||
|
@Column(name = "bounding_box_y_mm", precision = 10, scale = 3)
|
||||||
|
private BigDecimal boundingBoxYMm;
|
||||||
|
|
||||||
|
@Column(name = "bounding_box_z_mm", precision = 10, scale = 3)
|
||||||
|
private BigDecimal boundingBoxZMm;
|
||||||
|
|
||||||
|
@Column(name = "print_time_seconds")
|
||||||
|
private Integer printTimeSeconds;
|
||||||
|
|
||||||
|
@Column(name = "material_grams", precision = 12, scale = 2)
|
||||||
|
private BigDecimal materialGrams;
|
||||||
|
|
||||||
|
@Column(name = "unit_price_chf", precision = 12, scale = 2)
|
||||||
|
private BigDecimal unitPriceChf;
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "pricing_breakdown")
|
||||||
|
private Map<String, Object> pricingBreakdown;
|
||||||
|
|
||||||
|
@Column(name = "error_message", length = Integer.MAX_VALUE)
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private OffsetDateTime updatedAt;
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuoteSession getQuoteSession() {
|
||||||
|
return quoteSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuoteSession(QuoteSession quoteSession) {
|
||||||
|
this.quoteSession = quoteSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOriginalFilename() {
|
||||||
|
return originalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOriginalFilename(String originalFilename) {
|
||||||
|
this.originalFilename = originalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(Integer quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorCode() {
|
||||||
|
return colorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorCode(String colorCode) {
|
||||||
|
this.colorCode = colorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getBoundingBoxXMm() {
|
||||||
|
return boundingBoxXMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoundingBoxXMm(BigDecimal boundingBoxXMm) {
|
||||||
|
this.boundingBoxXMm = boundingBoxXMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getBoundingBoxYMm() {
|
||||||
|
return boundingBoxYMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoundingBoxYMm(BigDecimal boundingBoxYMm) {
|
||||||
|
this.boundingBoxYMm = boundingBoxYMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getBoundingBoxZMm() {
|
||||||
|
return boundingBoxZMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoundingBoxZMm(BigDecimal boundingBoxZMm) {
|
||||||
|
this.boundingBoxZMm = boundingBoxZMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPrintTimeSeconds() {
|
||||||
|
return printTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrintTimeSeconds(Integer printTimeSeconds) {
|
||||||
|
this.printTimeSeconds = printTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaterialGrams() {
|
||||||
|
return materialGrams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaterialGrams(BigDecimal materialGrams) {
|
||||||
|
this.materialGrams = materialGrams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getUnitPriceChf() {
|
||||||
|
return unitPriceChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnitPriceChf(BigDecimal unitPriceChf) {
|
||||||
|
this.unitPriceChf = unitPriceChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getPricingBreakdown() {
|
||||||
|
return pricingBreakdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPricingBreakdown(Map<String, Object> pricingBreakdown) {
|
||||||
|
this.pricingBreakdown = pricingBreakdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorMessage(String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "quote_sessions", indexes = {
|
||||||
|
@Index(name = "ix_quote_sessions_status",
|
||||||
|
columnList = "status"),
|
||||||
|
@Index(name = "ix_quote_sessions_expires_at",
|
||||||
|
columnList = "expires_at")})
|
||||||
|
public class QuoteSession {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "quote_session_id", nullable = false)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "pricing_version", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String pricingVersion;
|
||||||
|
|
||||||
|
@Column(name = "material_code", nullable = false, length = Integer.MAX_VALUE)
|
||||||
|
private String materialCode;
|
||||||
|
|
||||||
|
@Column(name = "nozzle_diameter_mm", precision = 5, scale = 2)
|
||||||
|
private BigDecimal nozzleDiameterMm;
|
||||||
|
|
||||||
|
@Column(name = "layer_height_mm", precision = 6, scale = 3)
|
||||||
|
private BigDecimal layerHeightMm;
|
||||||
|
|
||||||
|
@Column(name = "infill_pattern", length = Integer.MAX_VALUE)
|
||||||
|
private String infillPattern;
|
||||||
|
|
||||||
|
@Column(name = "infill_percent")
|
||||||
|
private Integer infillPercent;
|
||||||
|
|
||||||
|
@ColumnDefault("false")
|
||||||
|
@Column(name = "supports_enabled", nullable = false)
|
||||||
|
private Boolean supportsEnabled;
|
||||||
|
|
||||||
|
@Column(name = "notes", length = Integer.MAX_VALUE)
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@ColumnDefault("0.00")
|
||||||
|
@Column(name = "setup_cost_chf", nullable = false, precision = 12, scale = 2)
|
||||||
|
private BigDecimal setupCostChf;
|
||||||
|
|
||||||
|
@ColumnDefault("now()")
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
@Column(name = "expires_at", nullable = false)
|
||||||
|
private OffsetDateTime expiresAt;
|
||||||
|
|
||||||
|
@Column(name = "converted_order_id")
|
||||||
|
private UUID convertedOrderId;
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPricingVersion() {
|
||||||
|
return pricingVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPricingVersion(String pricingVersion) {
|
||||||
|
this.pricingVersion = pricingVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMaterialCode() {
|
||||||
|
return materialCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaterialCode(String materialCode) {
|
||||||
|
this.materialCode = materialCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getNozzleDiameterMm() {
|
||||||
|
return nozzleDiameterMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
|
||||||
|
this.nozzleDiameterMm = nozzleDiameterMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getLayerHeightMm() {
|
||||||
|
return layerHeightMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLayerHeightMm(BigDecimal layerHeightMm) {
|
||||||
|
this.layerHeightMm = layerHeightMm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInfillPattern() {
|
||||||
|
return infillPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfillPattern(String infillPattern) {
|
||||||
|
this.infillPattern = infillPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getInfillPercent() {
|
||||||
|
return infillPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfillPercent(Integer infillPercent) {
|
||||||
|
this.infillPercent = infillPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSupportsEnabled() {
|
||||||
|
return supportsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSupportsEnabled(Boolean supportsEnabled) {
|
||||||
|
this.supportsEnabled = supportsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotes() {
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotes(String notes) {
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getSetupCostChf() {
|
||||||
|
return setupCostChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSetupCostChf(BigDecimal setupCostChf) {
|
||||||
|
this.setupCostChf = setupCostChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getExpiresAt() {
|
||||||
|
return expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiresAt(OffsetDateTime expiresAt) {
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getConvertedOrderId() {
|
||||||
|
return convertedOrderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConvertedOrderId(UUID convertedOrderId) {
|
||||||
|
this.convertedOrderId = convertedOrderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import org.hibernate.annotations.Immutable;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Immutable
|
||||||
|
@Table(name = "quote_session_totals")
|
||||||
|
public class QuoteSessionTotal {
|
||||||
|
@Column(name = "quote_session_id")
|
||||||
|
private UUID quoteSessionId;
|
||||||
|
|
||||||
|
@Column(name = "total_chf")
|
||||||
|
private BigDecimal totalChf;
|
||||||
|
|
||||||
|
public UUID getQuoteSessionId() {
|
||||||
|
return quoteSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getTotalChf() {
|
||||||
|
return totalChf;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.domain.AbstractAuditable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.repository.NoRepositoryBean;
|
||||||
|
|
||||||
|
@NoRepositoryBean
|
||||||
|
public interface AbstractAuditableRepository<T extends AbstractAuditable> extends JpaRepository<T, PK> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.repository.NoRepositoryBean;
|
||||||
|
|
||||||
|
@NoRepositoryBean
|
||||||
|
public interface AbstractPersistableRepository<T extends AbstractPersistable> extends JpaRepository<T, PK> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.CustomQuoteRequestAttachment;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CustomQuoteRequestAttachmentRepository extends JpaRepository<CustomQuoteRequestAttachment, UUID> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.CustomQuoteRequest;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CustomQuoteRequestRepository extends JpaRepository<CustomQuoteRequest, UUID> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.Customer;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CustomerRepository extends JpaRepository<Customer, UUID> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.FilamentVariantStockKg;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface FilamentVariantStockKgRepository extends JpaRepository<FilamentVariantStockKg, Long> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.OrderItem;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface OrderItemRepository extends JpaRepository<OrderItem, UUID> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.Order;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface OrderRepository extends JpaRepository<Order, UUID> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.Payment;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface PaymentRepository extends JpaRepository<Payment, UUID> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.PrinterFleetCurrent;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface PrinterFleetCurrentRepository extends JpaRepository<PrinterFleetCurrent, Long> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.QuoteLineItem;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface QuoteLineItemRepository extends JpaRepository<QuoteLineItem, UUID> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.printcalculator.repository;
|
||||||
|
|
||||||
|
import com.printcalculator.entity.QuoteSession;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface QuoteSessionRepository extends JpaRepository<QuoteSession, UUID> {
|
||||||
|
}
|
||||||
558
db.sql
558
db.sql
@@ -16,11 +16,10 @@ create table printer_machine
|
|||||||
);
|
);
|
||||||
|
|
||||||
create view printer_fleet_current as
|
create view printer_fleet_current as
|
||||||
select 1 as fleet_id,
|
select case
|
||||||
case
|
|
||||||
when sum(fleet_weight) = 0 then null
|
when sum(fleet_weight) = 0 then null
|
||||||
else round(sum(power_watts * fleet_weight) / sum(fleet_weight))::integer
|
else round(sum(power_watts * fleet_weight) / sum(fleet_weight))::integer
|
||||||
end as weighted_average_power_watts,
|
end as weighted_average_power_watts,
|
||||||
max(build_volume_x_mm) as fleet_max_build_x_mm,
|
max(build_volume_x_mm) as fleet_max_build_x_mm,
|
||||||
max(build_volume_y_mm) as fleet_max_build_y_mm,
|
max(build_volume_y_mm) as fleet_max_build_y_mm,
|
||||||
max(build_volume_z_mm) as fleet_max_build_z_mm
|
max(build_volume_z_mm) as fleet_max_build_z_mm
|
||||||
@@ -156,54 +155,63 @@ begin;
|
|||||||
|
|
||||||
set timezone = 'Europe/Zurich';
|
set timezone = 'Europe/Zurich';
|
||||||
|
|
||||||
is_active = excluded.is_active;
|
-- =========================================================
|
||||||
|
-- 0) (Solo se non esiste) tabella infill_pattern + seed
|
||||||
|
-- =========================================================
|
||||||
|
-- Se la tabella esiste già, commenta questo blocco.
|
||||||
|
create table if not exists infill_pattern
|
||||||
|
(
|
||||||
|
infill_pattern_id bigserial primary key,
|
||||||
|
pattern_code text not null unique, -- es: grid, gyroid
|
||||||
|
display_name text not null,
|
||||||
|
is_active boolean not null default true
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into infill_pattern (pattern_code, display_name, is_active)
|
||||||
|
values ('grid', 'Grid', true),
|
||||||
|
('gyroid', 'Gyroid', true)
|
||||||
|
on conflict (pattern_code) do update
|
||||||
|
set display_name = excluded.display_name,
|
||||||
|
is_active = excluded.is_active;
|
||||||
|
|
||||||
|
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
-- 1) Pricing policy (valori ESATTI da Excel)
|
-- 1) Pricing policy (valori ESATTI da Excel)
|
||||||
-- Valid from: 2026-01-01, valid_to: NULL
|
-- Valid from: 2026-01-01, valid_to: NULL
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
insert into pricing_policy (
|
insert into pricing_policy (policy_name,
|
||||||
policy_name,
|
valid_from,
|
||||||
valid_from,
|
valid_to,
|
||||||
valid_to,
|
electricity_cost_chf_per_kwh,
|
||||||
electricity_cost_chf_per_kwh,
|
markup_percent,
|
||||||
markup_percent,
|
fixed_job_fee_chf,
|
||||||
fixed_job_fee_chf,
|
nozzle_change_base_fee_chf,
|
||||||
nozzle_change_base_fee_chf,
|
cad_cost_chf_per_hour,
|
||||||
cad_cost_chf_per_hour,
|
is_active)
|
||||||
is_active
|
values ('Excel Tariffe 2026-01-01',
|
||||||
) values (
|
'2026-01-01 00:00:00+01'::timestamptz,
|
||||||
'Excel Tariffe 2026-01-01',
|
null,
|
||||||
'2026-01-01 00:00:00+01'::timestamptz,
|
0.156, -- Costo elettricità CHF/kWh (Excel)
|
||||||
null,
|
0.000, -- Markup non specificato -> 0 (puoi cambiarlo dopo)
|
||||||
0.156, -- Costo elettricità CHF/kWh (Excel)
|
1.00, -- Costo fisso macchina CHF (Excel)
|
||||||
0.000, -- Markup non specificato -> 0 (puoi cambiarlo dopo)
|
0.00, -- Base cambio ugello: non specificato -> 0
|
||||||
1.00, -- Costo fisso macchina CHF (Excel)
|
25.00, -- Tariffa CAD CHF/h (Excel)
|
||||||
0.00, -- Base cambio ugello: non specificato -> 0
|
true)
|
||||||
25.00, -- Tariffa CAD CHF/h (Excel)
|
|
||||||
true
|
|
||||||
)
|
|
||||||
on conflict do nothing;
|
on conflict do nothing;
|
||||||
|
|
||||||
-- scaglioni tariffa stampa (Excel)
|
-- scaglioni tariffa stampa (Excel)
|
||||||
insert into pricing_policy_machine_hour_tier (
|
insert into pricing_policy_machine_hour_tier (pricing_policy_id,
|
||||||
pricing_policy_id,
|
tier_start_hours,
|
||||||
tier_start_hours,
|
tier_end_hours,
|
||||||
tier_end_hours,
|
machine_cost_chf_per_hour)
|
||||||
machine_cost_chf_per_hour
|
select p.pricing_policy_id,
|
||||||
)
|
tiers.tier_start_hours,
|
||||||
select
|
tiers.tier_end_hours,
|
||||||
p.pricing_policy_id,
|
tiers.machine_cost_chf_per_hour
|
||||||
tiers.tier_start_hours,
|
|
||||||
tiers.tier_end_hours,
|
|
||||||
tiers.machine_cost_chf_per_hour
|
|
||||||
from pricing_policy p
|
from pricing_policy p
|
||||||
cross join (
|
cross join (values (0.00::numeric, 10.00::numeric, 2.00::numeric), -- 0–10 h
|
||||||
values
|
(10.00::numeric, 20.00::numeric, 1.40::numeric), -- 10–20 h
|
||||||
(0.00::numeric, 10.00::numeric, 2.00::numeric), -- 0–10 h
|
(20.00::numeric, null::numeric, 0.50::numeric) -- >20 h
|
||||||
(10.00::numeric, 20.00::numeric, 1.40::numeric), -- 10–20 h
|
|
||||||
(20.00::numeric, null::numeric, 0.50::numeric) -- >20 h
|
|
||||||
) as tiers(tier_start_hours, tier_end_hours, machine_cost_chf_per_hour)
|
) as tiers(tier_start_hours, tier_end_hours, machine_cost_chf_per_hour)
|
||||||
where p.policy_name = 'Excel Tariffe 2026-01-01'
|
where p.policy_name = 'Excel Tariffe 2026-01-01'
|
||||||
on conflict do nothing;
|
on conflict do nothing;
|
||||||
@@ -212,52 +220,45 @@ on conflict do nothing;
|
|||||||
-- =========================================================
|
-- =========================================================
|
||||||
-- 2) Stampante: BambuLab A1
|
-- 2) Stampante: BambuLab A1
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
insert into printer_machine (
|
insert into printer_machine (printer_display_name,
|
||||||
printer_display_name,
|
build_volume_x_mm,
|
||||||
build_volume_x_mm,
|
build_volume_y_mm,
|
||||||
build_volume_y_mm,
|
build_volume_z_mm,
|
||||||
build_volume_z_mm,
|
power_watts,
|
||||||
power_watts,
|
fleet_weight,
|
||||||
fleet_weight,
|
is_active)
|
||||||
is_active
|
values ('BambuLab A1',
|
||||||
) values (
|
256,
|
||||||
'BambuLab A1',
|
256,
|
||||||
256,
|
256,
|
||||||
256,
|
150, -- hai detto "150, 140": qui ho messo 150
|
||||||
256,
|
1.000,
|
||||||
150, -- hai detto "150, 140": qui ho messo 150
|
true)
|
||||||
1.000,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
on conflict (printer_display_name) do update
|
on conflict (printer_display_name) do update
|
||||||
set
|
set build_volume_x_mm = excluded.build_volume_x_mm,
|
||||||
build_volume_x_mm = excluded.build_volume_x_mm,
|
|
||||||
build_volume_y_mm = excluded.build_volume_y_mm,
|
build_volume_y_mm = excluded.build_volume_y_mm,
|
||||||
build_volume_z_mm = excluded.build_volume_z_mm,
|
build_volume_z_mm = excluded.build_volume_z_mm,
|
||||||
power_watts = excluded.power_watts,
|
power_watts = excluded.power_watts,
|
||||||
fleet_weight = excluded.fleet_weight,
|
fleet_weight = excluded.fleet_weight,
|
||||||
is_active = excluded.is_active;
|
is_active = excluded.is_active;
|
||||||
|
|
||||||
|
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
-- 3) Material types (da Excel) - per ora niente technical
|
-- 3) Material types (da Excel) - per ora niente technical
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
insert into filament_material_type (
|
insert into filament_material_type (material_code,
|
||||||
material_code,
|
is_flexible,
|
||||||
is_flexible,
|
is_technical,
|
||||||
is_technical,
|
technical_type_label)
|
||||||
technical_type_label
|
values ('PLA', false, false, null),
|
||||||
) values
|
('PETG', false, false, null),
|
||||||
('PLA', false, false, null),
|
('TPU', true, false, null),
|
||||||
('PETG', false, false, null),
|
('ABS', false, false, null),
|
||||||
('TPU', true, false, null),
|
('Nylon', false, false, null),
|
||||||
('ABS', false, false, null),
|
('Carbon PLA', false, false, null)
|
||||||
('Nylon', false, false, null),
|
|
||||||
('Carbon PLA', false, false, null)
|
|
||||||
on conflict (material_code) do update
|
on conflict (material_code) do update
|
||||||
set
|
set is_flexible = excluded.is_flexible,
|
||||||
is_flexible = excluded.is_flexible,
|
is_technical = excluded.is_technical,
|
||||||
is_technical = excluded.is_technical,
|
|
||||||
technical_type_label = excluded.technical_type_label;
|
technical_type_label = excluded.technical_type_label;
|
||||||
|
|
||||||
|
|
||||||
@@ -268,99 +269,358 @@ on conflict (material_code) do update
|
|||||||
-- =========================================================
|
-- =========================================================
|
||||||
|
|
||||||
-- helper: ID PLA
|
-- helper: ID PLA
|
||||||
with pla as (
|
with pla as (select filament_material_type_id
|
||||||
select filament_material_type_id
|
from filament_material_type
|
||||||
from filament_material_type
|
where material_code = 'PLA')
|
||||||
where material_code = 'PLA'
|
insert
|
||||||
)
|
into filament_variant (filament_material_type_id,
|
||||||
insert into filament_variant (
|
variant_display_name,
|
||||||
filament_material_type_id,
|
color_name,
|
||||||
variant_display_name,
|
is_matte,
|
||||||
color_name,
|
is_special,
|
||||||
is_matte,
|
cost_chf_per_kg,
|
||||||
is_special,
|
stock_spools,
|
||||||
cost_chf_per_kg,
|
spool_net_kg,
|
||||||
stock_spools,
|
is_active)
|
||||||
spool_net_kg,
|
select pla.filament_material_type_id,
|
||||||
is_active
|
v.variant_display_name,
|
||||||
)
|
v.color_name,
|
||||||
select
|
v.is_matte,
|
||||||
pla.filament_material_type_id,
|
v.is_special,
|
||||||
v.variant_display_name,
|
18.00, -- PLA da Excel
|
||||||
v.color_name,
|
v.stock_spools,
|
||||||
v.is_matte,
|
1.000,
|
||||||
v.is_special,
|
true
|
||||||
18.00, -- PLA da Excel
|
|
||||||
v.stock_spools,
|
|
||||||
1.000,
|
|
||||||
true
|
|
||||||
from pla
|
from pla
|
||||||
cross join (
|
cross join (values ('PLA Bianco', 'Bianco', false, false, 3.000::numeric),
|
||||||
values
|
('PLA Nero', 'Nero', false, false, 3.000::numeric),
|
||||||
('PLA Bianco', 'Bianco', false, false, 3.000::numeric),
|
('PLA Blu', 'Blu', false, false, 1.000::numeric),
|
||||||
('PLA Nero', 'Nero', false, false, 3.000::numeric),
|
('PLA Arancione', 'Arancione', false, false, 1.000::numeric),
|
||||||
('PLA Blu', 'Blu', false, false, 1.000::numeric),
|
('PLA Grigio', 'Grigio', false, false, 1.000::numeric),
|
||||||
('PLA Arancione', 'Arancione', false, false, 1.000::numeric),
|
('PLA Grigio Scuro', 'Grigio scuro', false, false, 1.000::numeric),
|
||||||
('PLA Grigio', 'Grigio', false, false, 1.000::numeric),
|
('PLA Grigio Chiaro', 'Grigio chiaro', false, false, 1.000::numeric),
|
||||||
('PLA Grigio Scuro', 'Grigio scuro', false, false, 1.000::numeric),
|
('PLA Viola', 'Viola', false, false,
|
||||||
('PLA Grigio Chiaro', 'Grigio chiaro', false, false, 1.000::numeric),
|
1.000::numeric)) as v(variant_display_name, color_name, is_matte, is_special, stock_spools)
|
||||||
('PLA Viola', 'Viola', false, false, 1.000::numeric)
|
|
||||||
) as v(variant_display_name, color_name, is_matte, is_special, stock_spools)
|
|
||||||
on conflict (filament_material_type_id, variant_display_name) do update
|
on conflict (filament_material_type_id, variant_display_name) do update
|
||||||
set
|
set color_name = excluded.color_name,
|
||||||
color_name = excluded.color_name,
|
is_matte = excluded.is_matte,
|
||||||
is_matte = excluded.is_matte,
|
is_special = excluded.is_special,
|
||||||
is_special = excluded.is_special,
|
|
||||||
cost_chf_per_kg = excluded.cost_chf_per_kg,
|
cost_chf_per_kg = excluded.cost_chf_per_kg,
|
||||||
stock_spools = excluded.stock_spools,
|
stock_spools = excluded.stock_spools,
|
||||||
spool_net_kg = excluded.spool_net_kg,
|
spool_net_kg = excluded.spool_net_kg,
|
||||||
is_active = excluded.is_active;
|
is_active = excluded.is_active;
|
||||||
|
|
||||||
|
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
-- 5) Ugelli
|
-- 5) Ugelli
|
||||||
-- 0.4 standard (0 extra), 0.6 con attivazione 50 CHF
|
-- 0.4 standard (0 extra), 0.6 con attivazione 50 CHF
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
insert into nozzle_option (
|
insert into nozzle_option (nozzle_diameter_mm,
|
||||||
nozzle_diameter_mm,
|
owned_quantity,
|
||||||
owned_quantity,
|
extra_nozzle_change_fee_chf,
|
||||||
extra_nozzle_change_fee_chf,
|
is_active)
|
||||||
is_active
|
values (0.40, 1, 0.00, true),
|
||||||
) values
|
(0.60, 1, 50.00, true)
|
||||||
(0.40, 1, 0.00, true),
|
|
||||||
(0.60, 1, 50.00, true)
|
|
||||||
on conflict (nozzle_diameter_mm) do update
|
on conflict (nozzle_diameter_mm) do update
|
||||||
set
|
set owned_quantity = excluded.owned_quantity,
|
||||||
owned_quantity = excluded.owned_quantity,
|
|
||||||
extra_nozzle_change_fee_chf = excluded.extra_nozzle_change_fee_chf,
|
extra_nozzle_change_fee_chf = excluded.extra_nozzle_change_fee_chf,
|
||||||
is_active = excluded.is_active;
|
is_active = excluded.is_active;
|
||||||
|
|
||||||
|
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
-- 6) Layer heights (opzioni)
|
-- 6) Layer heights (opzioni)
|
||||||
-- =========================================================
|
-- =========================================================
|
||||||
insert into layer_height_option (
|
insert into layer_height_option (layer_height_mm,
|
||||||
layer_height_mm,
|
time_multiplier,
|
||||||
time_multiplier,
|
is_active)
|
||||||
is_active
|
values (0.080, 1.000, true),
|
||||||
) values
|
(0.120, 1.000, true),
|
||||||
(0.080, 1.000, true),
|
(0.160, 1.000, true),
|
||||||
(0.120, 1.000, true),
|
(0.200, 1.000, true),
|
||||||
(0.160, 1.000, true),
|
(0.240, 1.000, true),
|
||||||
(0.200, 1.000, true),
|
(0.280, 1.000, true)
|
||||||
(0.240, 1.000, true),
|
|
||||||
(0.280, 1.000, true)
|
|
||||||
on conflict (layer_height_mm) do update
|
on conflict (layer_height_mm) do update
|
||||||
set
|
set time_multiplier = excluded.time_multiplier,
|
||||||
time_multiplier = excluded.time_multiplier,
|
is_active = excluded.is_active;
|
||||||
is_active = excluded.is_active;
|
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Sostituisci __MULTIPLIER__ con il tuo valore (es. 1.10)
|
-- Sostituisci __MULTIPLIER__ con il tuo valore (es. 1.10)
|
||||||
update layer_height_option
|
update layer_height_option
|
||||||
set time_multiplier = 0.1
|
set time_multiplier = 0.1
|
||||||
where layer_height_mm = 0.080;
|
where layer_height_mm = 0.080;
|
||||||
|
|
||||||
|
|
||||||
|
-- =========================
|
||||||
|
-- CUSTOMERS (minimo indispensabile)
|
||||||
|
-- =========================
|
||||||
|
CREATE TABLE IF NOT EXISTS customers
|
||||||
|
(
|
||||||
|
customer_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
customer_type text NOT NULL CHECK (customer_type IN ('PRIVATE', 'COMPANY')),
|
||||||
|
email text NOT NULL,
|
||||||
|
phone text,
|
||||||
|
|
||||||
|
-- per PRIVATE
|
||||||
|
first_name text,
|
||||||
|
last_name text,
|
||||||
|
|
||||||
|
-- per COMPANY
|
||||||
|
company_name text,
|
||||||
|
contact_person text,
|
||||||
|
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS ux_customers_email
|
||||||
|
ON customers (lower(email));
|
||||||
|
|
||||||
|
-- =========================
|
||||||
|
-- QUOTE SESSIONS (carrello preventivo)
|
||||||
|
-- =========================
|
||||||
|
CREATE TABLE IF NOT EXISTS quote_sessions
|
||||||
|
(
|
||||||
|
quote_session_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status text NOT NULL CHECK (status IN ('ACTIVE', 'EXPIRED', 'CONVERTED')),
|
||||||
|
pricing_version text NOT NULL,
|
||||||
|
|
||||||
|
-- Parametri "globali" (dalla tua UI avanzata)
|
||||||
|
material_code text NOT NULL, -- es: PLA, PETG...
|
||||||
|
nozzle_diameter_mm numeric(5, 2), -- es: 0.40
|
||||||
|
layer_height_mm numeric(6, 3), -- es: 0.20
|
||||||
|
infill_pattern text, -- es: grid
|
||||||
|
infill_percent integer CHECK (infill_percent BETWEEN 0 AND 100),
|
||||||
|
supports_enabled boolean NOT NULL DEFAULT false,
|
||||||
|
notes text,
|
||||||
|
|
||||||
|
setup_cost_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||||
|
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
expires_at timestamptz NOT NULL,
|
||||||
|
converted_order_id uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_quote_sessions_status
|
||||||
|
ON quote_sessions (status);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_quote_sessions_expires_at
|
||||||
|
ON quote_sessions (expires_at);
|
||||||
|
|
||||||
|
-- =========================
|
||||||
|
-- QUOTE LINE ITEMS (1 file = 1 riga)
|
||||||
|
-- =========================
|
||||||
|
CREATE TABLE IF NOT EXISTS quote_line_items
|
||||||
|
(
|
||||||
|
quote_line_item_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
quote_session_id uuid NOT NULL REFERENCES quote_sessions (quote_session_id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
status text NOT NULL CHECK (status IN ('CALCULATING', 'READY', 'FAILED')),
|
||||||
|
|
||||||
|
original_filename text NOT NULL,
|
||||||
|
quantity integer NOT NULL DEFAULT 1 CHECK (quantity >= 1),
|
||||||
|
color_code text, -- es: white/black o codice interno
|
||||||
|
|
||||||
|
-- Output slicing / calcolo
|
||||||
|
bounding_box_x_mm numeric(10, 3),
|
||||||
|
bounding_box_y_mm numeric(10, 3),
|
||||||
|
bounding_box_z_mm numeric(10, 3),
|
||||||
|
print_time_seconds integer CHECK (print_time_seconds >= 0),
|
||||||
|
material_grams numeric(12, 2) CHECK (material_grams >= 0),
|
||||||
|
|
||||||
|
unit_price_chf numeric(12, 2) CHECK (unit_price_chf >= 0),
|
||||||
|
pricing_breakdown jsonb, -- opzionale: costi dettagliati senza creare tabelle
|
||||||
|
|
||||||
|
error_message text,
|
||||||
|
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_quote_line_items_session
|
||||||
|
ON quote_line_items (quote_session_id);
|
||||||
|
|
||||||
|
-- Vista utile per totale quote
|
||||||
|
CREATE OR REPLACE VIEW quote_session_totals AS
|
||||||
|
SELECT qs.quote_session_id,
|
||||||
|
qs.setup_cost_chf +
|
||||||
|
COALESCE(SUM(qli.unit_price_chf * qli.quantity), 0.00) AS total_chf
|
||||||
|
FROM quote_sessions qs
|
||||||
|
LEFT JOIN quote_line_items qli
|
||||||
|
ON qli.quote_session_id = qs.quote_session_id
|
||||||
|
AND qli.status = 'READY'
|
||||||
|
GROUP BY qs.quote_session_id;
|
||||||
|
|
||||||
|
-- =========================
|
||||||
|
-- ORDERS
|
||||||
|
-- =========================
|
||||||
|
CREATE TABLE IF NOT EXISTS orders
|
||||||
|
(
|
||||||
|
order_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
source_quote_session_id uuid REFERENCES quote_sessions (quote_session_id),
|
||||||
|
|
||||||
|
status text NOT NULL CHECK (status IN (
|
||||||
|
'PENDING_PAYMENT', 'PAID', 'IN_PRODUCTION',
|
||||||
|
'SHIPPED', 'COMPLETED', 'CANCELLED'
|
||||||
|
)),
|
||||||
|
|
||||||
|
customer_id uuid REFERENCES customers (customer_id),
|
||||||
|
customer_email text NOT NULL,
|
||||||
|
customer_phone text,
|
||||||
|
|
||||||
|
-- Snapshot indirizzo/fatturazione (evita tabella addresses e mantiene storico)
|
||||||
|
billing_customer_type text NOT NULL CHECK (billing_customer_type IN ('PRIVATE', 'COMPANY')),
|
||||||
|
billing_first_name text,
|
||||||
|
billing_last_name text,
|
||||||
|
billing_company_name text,
|
||||||
|
billing_contact_person text,
|
||||||
|
|
||||||
|
billing_address_line1 text NOT NULL,
|
||||||
|
billing_address_line2 text,
|
||||||
|
billing_zip text NOT NULL,
|
||||||
|
billing_city text NOT NULL,
|
||||||
|
billing_country_code char(2) NOT NULL DEFAULT 'CH',
|
||||||
|
|
||||||
|
shipping_same_as_billing boolean NOT NULL DEFAULT true,
|
||||||
|
shipping_first_name text,
|
||||||
|
shipping_last_name text,
|
||||||
|
shipping_company_name text,
|
||||||
|
shipping_contact_person text,
|
||||||
|
shipping_address_line1 text,
|
||||||
|
shipping_address_line2 text,
|
||||||
|
shipping_zip text,
|
||||||
|
shipping_city text,
|
||||||
|
shipping_country_code char(2),
|
||||||
|
|
||||||
|
currency char(3) NOT NULL DEFAULT 'CHF',
|
||||||
|
setup_cost_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||||
|
shipping_cost_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||||
|
discount_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||||
|
|
||||||
|
subtotal_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||||
|
total_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||||
|
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
paid_at timestamptz
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_orders_status
|
||||||
|
ON orders (status);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_orders_customer_email
|
||||||
|
ON orders (lower(customer_email));
|
||||||
|
|
||||||
|
-- =========================
|
||||||
|
-- ORDER ITEMS (1 file 3D = 1 riga, file salvato su disco)
|
||||||
|
-- =========================
|
||||||
|
CREATE TABLE IF NOT EXISTS order_items
|
||||||
|
(
|
||||||
|
order_item_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
order_id uuid NOT NULL REFERENCES orders (order_id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
original_filename text NOT NULL,
|
||||||
|
stored_relative_path text NOT NULL, -- es: orders/<orderId>/3d-files/<orderItemId>/<uuid>.stl
|
||||||
|
stored_filename text NOT NULL, -- es: <uuid>.stl
|
||||||
|
|
||||||
|
file_size_bytes bigint CHECK (file_size_bytes >= 0),
|
||||||
|
mime_type text,
|
||||||
|
sha256_hex text, -- opzionale, utile anche per dedup interno
|
||||||
|
|
||||||
|
material_code text NOT NULL,
|
||||||
|
color_code text,
|
||||||
|
quantity integer NOT NULL DEFAULT 1 CHECK (quantity >= 1),
|
||||||
|
|
||||||
|
-- Snapshot output
|
||||||
|
print_time_seconds integer CHECK (print_time_seconds >= 0),
|
||||||
|
material_grams numeric(12, 2) CHECK (material_grams >= 0),
|
||||||
|
unit_price_chf numeric(12, 2) NOT NULL CHECK (unit_price_chf >= 0),
|
||||||
|
line_total_chf numeric(12, 2) NOT NULL CHECK (line_total_chf >= 0),
|
||||||
|
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_order_items_order
|
||||||
|
ON order_items (order_id);
|
||||||
|
|
||||||
|
-- =========================
|
||||||
|
-- PAYMENTS (supporta più tentativi / metodi)
|
||||||
|
-- =========================
|
||||||
|
CREATE TABLE IF NOT EXISTS payments
|
||||||
|
(
|
||||||
|
payment_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
order_id uuid NOT NULL REFERENCES orders (order_id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
method text NOT NULL CHECK (method IN ('QR_BILL', 'BANK_TRANSFER', 'TWINT', 'CARD', 'OTHER')),
|
||||||
|
status text NOT NULL CHECK (status IN ('PENDING', 'RECEIVED', 'FAILED', 'CANCELLED', 'REFUNDED')),
|
||||||
|
|
||||||
|
currency char(3) NOT NULL DEFAULT 'CHF',
|
||||||
|
amount_chf numeric(12, 2) NOT NULL CHECK (amount_chf >= 0),
|
||||||
|
|
||||||
|
-- riferimento pagamento (molto utile per QR bill / riconciliazione)
|
||||||
|
payment_reference text,
|
||||||
|
provider_transaction_id text,
|
||||||
|
|
||||||
|
qr_payload text, -- se vuoi salvare contenuto QR/Swiss QR bill
|
||||||
|
initiated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
received_at timestamptz
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_payments_order
|
||||||
|
ON payments (order_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_payments_reference
|
||||||
|
ON payments (payment_reference);
|
||||||
|
|
||||||
|
-- =========================
|
||||||
|
-- CUSTOM QUOTE REQUESTS (preventivo personalizzato, form che hai mostrato)
|
||||||
|
-- =========================
|
||||||
|
CREATE TABLE IF NOT EXISTS custom_quote_requests
|
||||||
|
(
|
||||||
|
request_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
request_type text NOT NULL, -- es: "PREVENTIVO_PERSONALIZZATO" o come preferisci
|
||||||
|
|
||||||
|
customer_type text NOT NULL CHECK (customer_type IN ('PRIVATE', 'COMPANY')),
|
||||||
|
email text NOT NULL,
|
||||||
|
phone text,
|
||||||
|
|
||||||
|
-- PRIVATE
|
||||||
|
name text,
|
||||||
|
|
||||||
|
-- COMPANY
|
||||||
|
company_name text,
|
||||||
|
contact_person text,
|
||||||
|
|
||||||
|
message text NOT NULL,
|
||||||
|
status text NOT NULL CHECK (status IN ('NEW', 'PENDING', 'IN_PROGRESS', 'DONE', 'CLOSED')),
|
||||||
|
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_custom_quote_requests_status
|
||||||
|
ON custom_quote_requests (status);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_custom_quote_requests_email
|
||||||
|
ON custom_quote_requests (lower(email));
|
||||||
|
|
||||||
|
-- Allegati della richiesta (max 15 come UI)
|
||||||
|
CREATE TABLE IF NOT EXISTS custom_quote_request_attachments
|
||||||
|
(
|
||||||
|
attachment_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
request_id uuid NOT NULL REFERENCES custom_quote_requests (request_id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
original_filename text NOT NULL,
|
||||||
|
stored_relative_path text NOT NULL, -- es: quote-requests/<requestId>/attachments/<attachmentId>/<uuid>.stl
|
||||||
|
stored_filename text NOT NULL,
|
||||||
|
|
||||||
|
file_size_bytes bigint CHECK (file_size_bytes >= 0),
|
||||||
|
mime_type text,
|
||||||
|
sha256_hex text,
|
||||||
|
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_custom_quote_attachments_request
|
||||||
|
ON custom_quote_request_attachments (request_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user