From 8cd4c48e9594c3ec7fa7e4a251dc50d615fbef11 Mon Sep 17 00:00:00 2001 From: dnviti Date: Mon, 1 Dec 2025 10:00:40 +0100 Subject: [PATCH] - --- DEVELOPMENT.md | 151 +- frontend/package-lock.json | 17 + frontend/package.json | 1 + frontend/public/locales/en/translation.json | 294 +- frontend/public/locales/it/translation.json | 267 +- frontend/src/App.tsx | 30 + frontend/src/components/Layout.tsx | 21 + .../components/MrpConfigurationDialog.tsx | 90 + .../components/ProductionLayout.tsx | 47 + .../components/ProductionOrderPhases.tsx | 240 + .../pages/BillOfMaterialsFormPage.tsx | 352 ++ .../production/pages/BillOfMaterialsPage.tsx | 123 + .../src/modules/production/pages/MrpPage.tsx | 154 + .../pages/ProductionCycleFormPage.tsx | 362 ++ .../production/pages/ProductionCyclesPage.tsx | 119 + .../pages/ProductionDashboardPage.tsx | 131 + .../pages/ProductionOrderFormPage.tsx | 439 ++ .../production/pages/ProductionOrdersPage.tsx | 167 + .../production/pages/WorkCentersPage.tsx | 252 + frontend/src/modules/production/routes.tsx | 41 + .../production/services/productionService.ts | 150 + .../src/modules/production/types/index.ts | 248 + .../purchases/pages/PurchaseOrderFormPage.tsx | 482 ++ .../purchases/pages/PurchaseOrdersPage.tsx | 174 + .../purchases/pages/SupplierFormPage.tsx | 315 ++ .../modules/purchases/pages/SuppliersPage.tsx | 145 + frontend/src/modules/purchases/routes.tsx | 18 + .../purchases/services/purchaseService.ts | 40 + .../purchases/services/supplierService.ts | 30 + frontend/src/modules/purchases/types/index.ts | 121 + .../sales/pages/SalesOrderFormPage.tsx | 455 ++ .../modules/sales/pages/SalesOrdersPage.tsx | 176 + frontend/src/modules/sales/routes.tsx | 13 + .../modules/sales/services/salesService.ts | 40 + frontend/src/modules/sales/types/index.ts | 77 + frontend/vite.config.ts | 14 + .../Controllers/BillOfMaterialsController.cs | 59 + .../Production/Controllers/MrpController.cs | 38 + .../Controllers/ProductionCyclesController.cs | 65 + .../Controllers/ProductionOrdersController.cs | 105 + .../Controllers/WorkCentersController.cs | 72 + .../Production/Dtos/BillOfMaterialsDto.cs | 22 + .../Dtos/CreateBillOfMaterialsDto.cs | 17 + .../Dtos/CreateProductionOrderDto.cs | 13 + .../Production/Dtos/MrpConfigurationDto.cs | 11 + .../Production/Dtos/ProductionCycleDto.cs | 65 + .../Production/Dtos/ProductionOrderDto.cs | 55 + .../Dtos/UpdateBillOfMaterialsDto.cs | 19 + .../Dtos/UpdateProductionOrderDto.cs | 12 + .../Modules/Production/Dtos/WorkCenterDto.cs | 27 + .../Production/Services/IMrpService.cs | 11 + .../Production/Services/IProductionService.cs | 37 + .../Modules/Production/Services/MrpService.cs | 260 + .../Production/Services/ProductionService.cs | 803 +++ .../Controllers/PurchaseOrdersController.cs | 106 + .../Controllers/SuppliersController.cs | 56 + .../Purchases/Dtos/PurchaseOrderDtos.cs | 82 + .../Modules/Purchases/Dtos/SupplierDtos.cs | 63 + .../Purchases/Services/PurchaseService.cs | 340 ++ .../Purchases/Services/SupplierService.cs | 166 + .../Controllers/SalesOrdersController.cs | 106 + .../Modules/Sales/Dtos/SalesOrderDtos.cs | 78 + .../Modules/Sales/Services/SalesService.cs | 324 ++ src/Apollinare.API/Program.cs | 14 + .../Services/AutoCodeService.cs | 6 + src/Apollinare.API/apollinare.db-shm | Bin 32768 -> 0 bytes src/Apollinare.API/apollinare.db-wal | 0 src/Apollinare.Domain/Entities/Cliente.cs | 1 + .../Entities/Production/BillOfMaterials.cs | 20 + .../Production/BillOfMaterialsComponent.cs | 18 + .../Entities/Production/MrpSuggestion.cs | 26 + .../Entities/Production/ProductionCycle.cs | 40 + .../Entities/Production/ProductionOrder.cs | 39 + .../Production/ProductionOrderComponent.cs | 15 + .../Production/ProductionOrderPhase.cs | 34 + .../Entities/Production/WorkCenter.cs | 12 + .../Entities/Purchases/PurchaseOrder.cs | 95 + .../Entities/Purchases/PurchaseOrderLine.cs | 59 + .../Entities/Purchases/Supplier.cs | 93 + .../Entities/Sales/SalesOrder.cs | 94 + .../Entities/Sales/SalesOrderLine.cs | 59 + .../Data/AppollinareDbContext.cs | 290 ++ ...51130134233_AddPurchasesModule.Designer.cs | 3570 ++++++++++++++ .../20251130134233_AddPurchasesModule.cs | 195 + .../20251130143646_AddSalesModule.Designer.cs | 3737 ++++++++++++++ .../20251130143646_AddSalesModule.cs | 126 + ...1130152222_AddProductionModule.Designer.cs | 4004 +++++++++++++++ .../20251130152222_AddProductionModule.cs | 207 + ...30161658_AddAdvancedProduction.Designer.cs | 4341 +++++++++++++++++ .../20251130161658_AddAdvancedProduction.cs | 251 + .../AppollinareDbContextModelSnapshot.cs | 1038 ++++ 91 files changed, 27185 insertions(+), 7 deletions(-) create mode 100644 frontend/src/modules/production/components/MrpConfigurationDialog.tsx create mode 100644 frontend/src/modules/production/components/ProductionLayout.tsx create mode 100644 frontend/src/modules/production/components/ProductionOrderPhases.tsx create mode 100644 frontend/src/modules/production/pages/BillOfMaterialsFormPage.tsx create mode 100644 frontend/src/modules/production/pages/BillOfMaterialsPage.tsx create mode 100644 frontend/src/modules/production/pages/MrpPage.tsx create mode 100644 frontend/src/modules/production/pages/ProductionCycleFormPage.tsx create mode 100644 frontend/src/modules/production/pages/ProductionCyclesPage.tsx create mode 100644 frontend/src/modules/production/pages/ProductionDashboardPage.tsx create mode 100644 frontend/src/modules/production/pages/ProductionOrderFormPage.tsx create mode 100644 frontend/src/modules/production/pages/ProductionOrdersPage.tsx create mode 100644 frontend/src/modules/production/pages/WorkCentersPage.tsx create mode 100644 frontend/src/modules/production/routes.tsx create mode 100644 frontend/src/modules/production/services/productionService.ts create mode 100644 frontend/src/modules/production/types/index.ts create mode 100644 frontend/src/modules/purchases/pages/PurchaseOrderFormPage.tsx create mode 100644 frontend/src/modules/purchases/pages/PurchaseOrdersPage.tsx create mode 100644 frontend/src/modules/purchases/pages/SupplierFormPage.tsx create mode 100644 frontend/src/modules/purchases/pages/SuppliersPage.tsx create mode 100644 frontend/src/modules/purchases/routes.tsx create mode 100644 frontend/src/modules/purchases/services/purchaseService.ts create mode 100644 frontend/src/modules/purchases/services/supplierService.ts create mode 100644 frontend/src/modules/purchases/types/index.ts create mode 100644 frontend/src/modules/sales/pages/SalesOrderFormPage.tsx create mode 100644 frontend/src/modules/sales/pages/SalesOrdersPage.tsx create mode 100644 frontend/src/modules/sales/routes.tsx create mode 100644 frontend/src/modules/sales/services/salesService.ts create mode 100644 frontend/src/modules/sales/types/index.ts create mode 100644 src/Apollinare.API/Modules/Production/Controllers/BillOfMaterialsController.cs create mode 100644 src/Apollinare.API/Modules/Production/Controllers/MrpController.cs create mode 100644 src/Apollinare.API/Modules/Production/Controllers/ProductionCyclesController.cs create mode 100644 src/Apollinare.API/Modules/Production/Controllers/ProductionOrdersController.cs create mode 100644 src/Apollinare.API/Modules/Production/Controllers/WorkCentersController.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/BillOfMaterialsDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/CreateBillOfMaterialsDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/CreateProductionOrderDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/MrpConfigurationDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/ProductionCycleDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/ProductionOrderDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/UpdateBillOfMaterialsDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/UpdateProductionOrderDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Dtos/WorkCenterDto.cs create mode 100644 src/Apollinare.API/Modules/Production/Services/IMrpService.cs create mode 100644 src/Apollinare.API/Modules/Production/Services/IProductionService.cs create mode 100644 src/Apollinare.API/Modules/Production/Services/MrpService.cs create mode 100644 src/Apollinare.API/Modules/Production/Services/ProductionService.cs create mode 100644 src/Apollinare.API/Modules/Purchases/Controllers/PurchaseOrdersController.cs create mode 100644 src/Apollinare.API/Modules/Purchases/Controllers/SuppliersController.cs create mode 100644 src/Apollinare.API/Modules/Purchases/Dtos/PurchaseOrderDtos.cs create mode 100644 src/Apollinare.API/Modules/Purchases/Dtos/SupplierDtos.cs create mode 100644 src/Apollinare.API/Modules/Purchases/Services/PurchaseService.cs create mode 100644 src/Apollinare.API/Modules/Purchases/Services/SupplierService.cs create mode 100644 src/Apollinare.API/Modules/Sales/Controllers/SalesOrdersController.cs create mode 100644 src/Apollinare.API/Modules/Sales/Dtos/SalesOrderDtos.cs create mode 100644 src/Apollinare.API/Modules/Sales/Services/SalesService.cs delete mode 100644 src/Apollinare.API/apollinare.db-shm delete mode 100644 src/Apollinare.API/apollinare.db-wal create mode 100644 src/Apollinare.Domain/Entities/Production/BillOfMaterials.cs create mode 100644 src/Apollinare.Domain/Entities/Production/BillOfMaterialsComponent.cs create mode 100644 src/Apollinare.Domain/Entities/Production/MrpSuggestion.cs create mode 100644 src/Apollinare.Domain/Entities/Production/ProductionCycle.cs create mode 100644 src/Apollinare.Domain/Entities/Production/ProductionOrder.cs create mode 100644 src/Apollinare.Domain/Entities/Production/ProductionOrderComponent.cs create mode 100644 src/Apollinare.Domain/Entities/Production/ProductionOrderPhase.cs create mode 100644 src/Apollinare.Domain/Entities/Production/WorkCenter.cs create mode 100644 src/Apollinare.Domain/Entities/Purchases/PurchaseOrder.cs create mode 100644 src/Apollinare.Domain/Entities/Purchases/PurchaseOrderLine.cs create mode 100644 src/Apollinare.Domain/Entities/Purchases/Supplier.cs create mode 100644 src/Apollinare.Domain/Entities/Sales/SalesOrder.cs create mode 100644 src/Apollinare.Domain/Entities/Sales/SalesOrderLine.cs create mode 100644 src/Apollinare.Infrastructure/Migrations/20251130134233_AddPurchasesModule.Designer.cs create mode 100644 src/Apollinare.Infrastructure/Migrations/20251130134233_AddPurchasesModule.cs create mode 100644 src/Apollinare.Infrastructure/Migrations/20251130143646_AddSalesModule.Designer.cs create mode 100644 src/Apollinare.Infrastructure/Migrations/20251130143646_AddSalesModule.cs create mode 100644 src/Apollinare.Infrastructure/Migrations/20251130152222_AddProductionModule.Designer.cs create mode 100644 src/Apollinare.Infrastructure/Migrations/20251130152222_AddProductionModule.cs create mode 100644 src/Apollinare.Infrastructure/Migrations/20251130161658_AddAdvancedProduction.Designer.cs create mode 100644 src/Apollinare.Infrastructure/Migrations/20251130161658_AddAdvancedProduction.cs diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e017ee1..3c83a59 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -54,12 +54,115 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve ## Quick Start - Session Recovery -**Ultima sessione:** 29 Novembre 2025 (pomeriggio) +**Ultima sessione:** 30 Novembre 2025 (Notte) -**Stato progetto:** Migrazione Oracle APEX → .NET + React TypeScript in corso +**Stato progetto:** Modulo Produzione COMPLETATO (incluso MRP ricorsivo e Dashboard). Prossimo step: Modulo Qualità o completamento Report System. **Lavoro completato nell'ultima sessione:** +- **NUOVA FEATURE: Modulo Produzione (production) - Avanzato** - COMPLETATO + - **Backend implementato:** + - Entities: `WorkCenter`, `ProductionCycle`, `ProductionCyclePhase`, `ProductionOrderPhase`, `MrpSuggestion` + - DTOs: `WorkCenterDto`, `ProductionCycleDto`, `ProductionOrderPhaseDto`, `MrpSuggestionDto` (con Create/Update variants) + - Services: `ProductionService` (esteso), `MrpService` + - Controllers: `WorkCentersController`, `ProductionCyclesController`, `MrpController` + - Migration: `AddAdvancedProduction` + - **Frontend implementato:** + - Pages: `WorkCentersPage`, `ProductionCyclesPage`, `ProductionCycleFormPage`, `MrpPage` + - Components: `ProductionOrderPhases` (gestione avanzamento), `ProductionLayout` (navigazione tab) + - Services: `productionService.ts` (esteso) + - Routing: `/production/work-centers`, `/production/cycles`, `/production/mrp` + - Traduzioni: Aggiunte chiavi per centri lavoro, cicli, fasi e MRP in `translation.json` (IT e EN) + - **Funzionalità:** + - **Centri di Lavoro**: Gestione risorse produttive e costi orari + - **Cicli Produttivi**: Definizione sequenze fasi standard per articolo + - **Ordini di Produzione**: + - Copia automatica fasi dal ciclo default alla creazione ordine + - Gestione avanzamento fasi (Start/Complete) con tempi e quantità + - **MRP (Material Requirements Planning)**: + - Calcolo fabbisogni basato su ordini clienti e scorte minime + - Generazione suggerimenti produzione/acquisto + - Processamento suggerimenti (creazione automatica ordini) + +- **FIX: Traduzioni Modulo Produzione** - RISOLTO + - **Problema:** Alcune chiavi di traduzione mancanti o errate nel componente `ProductionOrderPhases`. + - **Soluzione:** + - Aggiornato `ProductionOrderPhases.tsx` per usare le chiavi corrette (`production.order.phases.*`). + - Aggiunte le chiavi mancanti (`durationHelp`, stati fasi) in `it/translation.json` e `en/translation.json`. + - **File modificati:** `ProductionOrderPhases.tsx`, `it/translation.json`, `en/translation.json`. + +- **NUOVA FEATURE: Distinta Base Multilivello e MRP Ricorsivo** - COMPLETATO + - **Backend:** + - Aggiornato `MrpService` per calcolare i fabbisogni in modo ricorsivo (infiniti livelli). + - Aggiornato `ProductionService` per supportare la creazione automatica e ricorsiva degli ordini figli per i semilavorati. + - **Frontend:** + - Aggiunta opzione "Crea ordini figli" nel form di creazione Ordine di Produzione. + - Aggiornate traduzioni. + - **Nuova Dashboard Produzione:** Creata pagina con KPI e ordini recenti. + - **Visualizzazione Gerarchia:** Aggiunta colonna "Ordine Padre" nella lista e tab "Ordini Figli" nel dettaglio ordine. + +- **NUOVA FEATURE: MRP Configurabile e Distinta Base Multilivello** - COMPLETATO + - **Backend:** + - Aggiornato `MrpService` per supportare configurazione (Safety Stock, Sales Orders, Forecasts). + - Implementata logica MRP ricorsiva con gestione Lead Time e Safety Stock. + - Aggiunto `MrpConfigurationDto`. + - Aggiornato `MrpController` per accettare configurazione. + - **Frontend:** + - Creato `MrpConfigurationDialog` per impostare parametri MRP. + - Aggiornato `MrpPage` per usare il dialog. + - Aggiornato `productionService` per passare la configurazione. + - Aggiunte traduzioni per la configurazione MRP. + - **Test:** + - Verificata creazione articoli e BOM multilivello. + - Verificata esecuzione MRP (backend logica corretta). + +- **NUOVA FEATURE: Modulo Vendite (sales)** - COMPLETATO + - **Backend implementato:** + - Entities: `SalesOrder`, `SalesOrderLine` + - DTOs: `SalesOrderDto`, `SalesOrderLineDto` (con Create/Update variants) + - Services: `SalesService` (CRUD, Confirm, Ship logic) + - Controllers: `SalesOrdersController` + - Migration: `AddSalesModule` + - **Frontend implementato:** + - Pages: `SalesOrdersPage`, `SalesOrderFormPage` + - Services: `salesService.ts` + - Routing: `/sales/orders` + - Menu: Aggiunta voce "Vendite" nella sidebar + - Traduzioni: Aggiunte chiavi per ordini in `translation.json` (IT) + - **Funzionalità:** + - Gestione ordini di vendita + - Creazione ordini (Bozza -> Confermato -> Spedito) + - Spedizione merce (Ship) + - Calcolo totali ordine + +- **FIX: Traduzioni Modulo Vendite e Menu** - RISOLTO + - **Problema:** Chiavi di traduzione errate in `SalesOrderFormPage` e voci di menu mancanti in Inglese. + - **Soluzione:** + - Allineate chiavi `sales.orders.*` -> `sales.order.*` nel frontend. + - Aggiunta sezione `sales` completa in `en/translation.json`. + - Aggiunte voci menu "Purchases" e "Sales" in `en/translation.json`. + - **File modificati:** `SalesOrderFormPage.tsx`, `en/translation.json`, `it/translation.json`. + +- **NUOVA FEATURE: Modulo Acquisti (purchases)** - COMPLETATO + - **Backend implementato:** + - Entities: `Supplier`, `PurchaseOrder`, `PurchaseOrderLine` + - DTOs: `SupplierDto`, `PurchaseOrderDto`, `PurchaseOrderLineDto` (con Create/Update variants) + - Services: `SupplierService`, `PurchaseService` (CRUD, Confirm, Receive logic) + - Controllers: `SuppliersController`, `PurchaseOrdersController` + - Migration: `AddPurchasesModule` + - AutoCode: Integrazione per generazione codici `Supplier` e `PurchaseOrder` + - **Frontend implementato:** + - Pages: `SuppliersPage`, `SupplierFormPage`, `PurchaseOrdersPage`, `PurchaseOrderFormPage` + - Services: `supplierService.ts`, `purchaseService.ts` + - Routing: `/purchases/suppliers`, `/purchases/orders` + - Menu: Aggiunta voce "Acquisti" nella sidebar + - Traduzioni: Aggiunte chiavi per fornitori e ordini in `translation.json` (IT e EN) + - **Funzionalità:** + - Gestione anagrafica fornitori + - Creazione ordini di acquisto (Bozza -> Confermato -> Ricevuto) + - Ricezione merce con generazione automatica movimenti di magazzino (Inbound) + - Calcolo totali ordine (imponibile, IVA, totale) + - **NUOVA FEATURE: Supporto Tema Scuro e Multilingua** - COMPLETATO - **Obiettivo:** Permettere all'utente di cambiare tema (Chiaro/Scuro) e lingua (Italiano/Inglese) con persistenza - **Frontend implementato:** @@ -157,6 +260,17 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve - In modifica: campo Codice mostra il valore reale, sempre disabled - Campo "Codice Alternativo" sempre modificabile (opzionale) +- **FIX: API 404 / Pagine Bianche in Dev Mode** - RISOLTO + - **Problema:** Le chiamate API dal frontend fallivano con 404 (o ritornavano HTML) e le pagine rimanevano bianche. + - **Causa:** Mancava la configurazione del proxy in `vite.config.ts` per inoltrare le richieste `/api` al backend (.NET su porta 5000). + - **Soluzione:** Aggiunto proxy per `/api` e `/hubs` verso `http://localhost:5000` in `vite.config.ts`. + - **File modificati:** `frontend/vite.config.ts` + +- **FIX: Traduzioni Mancanti e Chiavi Errate** - RISOLTO + - **Problema:** Errori di interfaccia dovuti a chiavi di traduzione mancanti (`common.required`, `common.add`, `common.active`) e percorsi errati in `PurchaseOrderFormPage` e `SuppliersPage`. + - **Soluzione:** Aggiunte chiavi mancanti in `en/translation.json` e corretti i percorsi delle chiavi nei componenti React. + - **File modificati:** `frontend/public/locales/en/translation.json`, `frontend/src/modules/purchases/pages/PurchaseOrderFormPage.tsx`, `frontend/src/modules/purchases/pages/SuppliersPage.tsx` + **Lavoro completato nelle sessioni precedenti (30 Novembre 2025):** - **NUOVA FEATURE: Sistema Codici Automatici Configurabili** - COMPLETATO @@ -520,9 +634,12 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve - Backend: Entities, Service, Controllers, API completi - Manca: Frontend (pagine React per gestione articoli, movimenti, giacenze) 2. [x] **Frontend modulo Magazzino** - Pagine React per warehouse (Articoli, Movimenti, Giacenze, Inventario) -3. [ ] **Implementare modulo Acquisti (purchases)** - Dipende da Magazzino -4. [ ] **Implementare modulo Vendite (sales)** - Dipende da Magazzino -5. [ ] **Implementare modulo Produzione (production)** - Dipende da Magazzino +3. [x] **Implementare modulo Acquisti (purchases)** - COMPLETATO +4. [x] **Implementare modulo Vendite (sales)** - COMPLETATO +5. [x] **Implementare modulo Produzione (production)** - COMPLETATO + - Backend: Entities, Service, Controllers, API completi + - Frontend: Pagine React per BOM e Ordini, integrazione Magazzino + - Test: Verificato flusso completo (BOM -> Ordine -> Stati -> Completamento) 6. [ ] **Implementare modulo Qualità (quality)** - Indipendente **Report System (completamento):** @@ -558,6 +675,30 @@ make check # Verifica prerequisiti installati (dotnet, node, npm) - Frontend: in dev mode (`make frontend-run`) il hot-reload è automatico per la maggior parte delle modifiche, ma per modifiche strutturali (nuovi file, cambi a tipi, etc.) potrebbe essere necessario riavviare --- +# Development Documentation + +## Production Module Implementation +- **Backend**: + - Entities: `BillOfMaterials`, `BillOfMaterialsComponent`, `ProductionOrder`, `ProductionOrderComponent` + - Services: `ProductionService` (implements `IProductionService`) + - Controllers: `BillOfMaterialsController`, `ProductionOrdersController` + - Integration: Registered in `Program.cs`, added to `AppollinareDbContext` +- **Frontend**: + - Types: `frontend/src/modules/production/types/index.ts` + - Services: `frontend/src/modules/production/services/productionService.ts` + - Pages: + - `ProductionOrdersPage` (List) + - `ProductionOrderFormPage` (Create/Edit) + - `BillOfMaterialsPage` (List) + - `BillOfMaterialsFormPage` (Create/Edit) + - Routes: `frontend/src/modules/production/routes.tsx` + - Menu: Added to `Layout.tsx` with `Factory` icon + - Translations: Added `production` section to `it` and `en` locales + +## Recent Changes +- Fixed build errors in Purchases and Sales modules (types and unused imports). +- Implemented Production module with full CRUD and status management. +- Integrated Production module with Warehouse module for article selection. ## Project Overview diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 931ad97..ff09e47 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,6 +31,7 @@ "i18next-http-backend": "^3.0.2", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-hook-form": "^7.67.0", "react-i18next": "^16.3.5", "react-router-dom": "^7.9.6", "uuid": "^13.0.0", @@ -4973,6 +4974,22 @@ "react": "^19.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.67.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.67.0.tgz", + "integrity": "sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "16.3.5", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.3.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index fbb32ed..ae9b62a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,6 +33,7 @@ "i18next-http-backend": "^3.0.2", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-hook-form": "^7.67.0", "react-i18next": "^16.3.5", "react-router-dom": "^7.9.6", "uuid": "^13.0.0", diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 1af917b..6eb7a24 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -28,7 +28,12 @@ "optional": "Optional", "notes": "Notes", "preview": "Preview", - "none": "None" + "none": "None", + "view": "View", + "required": "Required", + "add": "Add", + "active": "Active", + "inactive": "Inactive" }, "menu": { "dashboard": "Dashboard", @@ -39,6 +44,9 @@ "articles": "Articles", "resources": "Resources", "warehouse": "Warehouse", + "purchases": "Purchases", + "sales": "Sales", + "production": "Production", "reports": "Reports", "modules": "Modules", "autoCodes": "Auto Codes", @@ -1045,5 +1053,289 @@ } } } + }, + "purchases": { + "menu": { + "suppliers": "Suppliers", + "orders": "Purchase Orders" + }, + "suppliers": { + "title": "Suppliers", + "newSupplier": "New Supplier", + "editSupplier": "Edit Supplier", + "columns": { + "code": "Code", + "name": "Name", + "vatNumber": "VAT Number", + "email": "Email", + "phone": "Phone", + "city": "City", + "status": "Status" + }, + "fields": { + "code": "Code", + "name": "Business Name", + "vatNumber": "VAT Number", + "fiscalCode": "Fiscal Code", + "email": "Email", + "pec": "PEC", + "phone": "Phone", + "website": "Website", + "address": "Address", + "city": "City", + "province": "Province", + "zipCode": "ZIP Code", + "country": "Country", + "paymentTerms": "Payment Terms", + "notes": "Notes", + "isActive": "Active" + }, + "placeholders": { + "search": "Search supplier...", + "generatedAutomatically": "Generated automatically" + }, + "deleteConfirm": "Are you sure you want to delete this supplier?" + }, + "orders": { + "title": "Purchase Orders", + "newOrder": "New Order", + "editOrder": "Edit Order", + "columns": { + "orderNumber": "Order Number", + "orderDate": "Date", + "supplier": "Supplier", + "status": "Status", + "total": "Total", + "deliveryDate": "Delivery Date" + }, + "fields": { + "orderNumber": "Order Number", + "orderDate": "Order Date", + "expectedDeliveryDate": "Expected Delivery", + "supplier": "Supplier", + "destinationWarehouse": "Destination Warehouse", + "notes": "Notes", + "article": "Article", + "quantity": "Quantity", + "unitPrice": "Unit Price", + "discount": "Discount %", + "taxRate": "Tax Rate %", + "lineTotal": "Total" + }, + "status": { + "Draft": "Draft", + "Confirmed": "Confirmed", + "Received": "Received", + "Cancelled": "Cancelled" + }, + "actions": { + "addLine": "Add Line", + "confirm": "Confirm Order", + "receive": "Receive Goods", + "view": "View", + "delete": "Delete" + }, + "totals": { + "net": "Net Total", + "tax": "Tax", + "gross": "Gross Total" + }, + "deleteConfirm": "Are you sure you want to delete this order?", + "confirmDialog": { + "title": "Confirm Order", + "content": "Are you sure you want to confirm this order? It will no longer be editable.", + "confirm": "Confirm", + "cancel": "Cancel" + }, + "receiveDialog": { + "title": "Receive Goods", + "content": "Are you sure you want to mark this order as received? This will generate stock movements.", + "confirm": "Receive", + "cancel": "Cancel" + } + } + }, + "sales": { + "order": { + "title": "Sales Orders", + "newOrder": "New Order", + "createTitle": "New Order", + "editTitle": "Edit Order", + "status": { + "Draft": "Draft", + "Confirmed": "Confirmed", + "PartiallyShipped": "Partially Shipped", + "Shipped": "Shipped", + "Invoiced": "Invoiced", + "Cancelled": "Cancelled" + }, + "columns": { + "number": "Number", + "date": "Date", + "customer": "Customer", + "status": "Status", + "total": "Total" + }, + "fields": { + "orderDate": "Order Date", + "expectedDeliveryDate": "Expected Delivery Date", + "customer": "Customer", + "notes": "Notes", + "lineTotal": "Line Total", + "article": "Article", + "quantity": "Quantity", + "unitPrice": "Unit Price", + "discount": "Discount %", + "taxRate": "Tax Rate %" + }, + "totals": { + "gross": "Gross Total" + }, + "actions": { + "confirm": "Confirm Order", + "ship": "Ship Goods" + } + } + }, + "production": { + "bom": { + "title": "Bills of Materials", + "newBom": "New BOM", + "createTitle": "New Bill of Materials", + "editTitle": "Edit Bill of Materials", + "fields": { + "name": "Name", + "description": "Description", + "article": "Produced Article", + "quantity": "Produced Quantity", + "components": "Components", + "componentArticle": "Component Article", + "componentQuantity": "Quantity", + "scrapPercentage": "Scrap %", + "noComponents": "No components added" + }, + "columns": { + "name": "Name", + "article": "Article", + "quantity": "Quantity" + } + }, + "dashboard": { + "title": "Production Dashboard", + "activeOrders": "Active Orders", + "lateOrders": "Late Orders", + "mrpSuggestions": "MRP Suggestions", + "completedToday": "Completed Today", + "recentOrders": "Recent Orders" + }, + "order": { + "title": "Production Orders", + "newOrder": "New Order", + "createTitle": "New Order", + "editTitle": "Edit Order", + "subOrders": "Sub-Orders", + "noSubOrders": "No sub-orders present", + "status": { + "Draft": "Draft", + "Planned": "Planned", + "Released": "Released", + "InProgress": "In Progress", + "Completed": "Completed", + "Cancelled": "Cancelled" + }, + "columns": { + "code": "Code", + "startDate": "Start Date", + "article": "Article", + "parentOrder": "Parent Order", + "quantity": "Quantity", + "status": "Status" + }, + "fields": { + "article": "Article to Produce", + "quantity": "Quantity", + "startDate": "Start Date", + "dueDate": "Due Date", + "notes": "Notes", + "bom": "Bill of Materials", + "bomHelp": "Select a BOM to pre-fill components", + "createChildOrders": "Create child orders for sub-assemblies" + }, + "actions": { + "plan": "Plan", + "release": "Release", + "start": "Start", + "complete": "Complete" + }, + "phases": { + "title": "Production Phases", + "sequence": "Seq", + "name": "Phase", + "workCenter": "Work Center", + "status": "Status", + "progress": "Progress", + "actions": "Actions", + "start": "Start Phase", + "complete": "Complete Phase", + "quantity": "Qty Completed", + "scrapped": "Qty Scrapped", + "duration": "Duration (min)", + "durationHelp": "Estimated: {{estimated}} min", + "statusValue": { + "Pending": "Pending", + "InProgress": "In Progress", + "Completed": "Completed", + "Paused": "Paused" + } + } + }, + "workCenter": { + "title": "Work Centers", + "new": "New Work Center", + "edit": "Edit Work Center", + "fields": { + "code": "Code", + "name": "Name", + "description": "Description", + "costPerHour": "Hourly Cost" + } + }, + "cycle": { + "title": "Production Cycles", + "new": "New Cycle", + "createTitle": "New Production Cycle", + "editTitle": "Edit Production Cycle", + "phases": "Cycle Phases", + "addPhase": "Add Phase", + "noPhases": "No phases defined", + "fields": { + "name": "Cycle Name", + "description": "Description", + "article": "Article", + "isDefault": "Default", + "phaseName": "Phase Name", + "workCenter": "Work Center", + "duration": "Unit Duration (min)", + "setupTime": "Setup Time (min)" + } + }, + "mrp": { + "title": "MRP Planning", + "run": "Run MRP", + "columns": { + "date": "Calculation Date", + "article": "Article", + "type": "Type", + "quantity": "Quantity", + "reason": "Reason" + }, + "type": { + "production": "Production", + "purchase": "Purchase" + }, + "actions": { + "process": "Process / Create Order" + } + } } } \ No newline at end of file diff --git a/frontend/public/locales/it/translation.json b/frontend/public/locales/it/translation.json index c1f9a52..5e9ae3e 100644 --- a/frontend/public/locales/it/translation.json +++ b/frontend/public/locales/it/translation.json @@ -28,7 +28,8 @@ "optional": "Opzionale", "notes": "Note", "preview": "Anteprima", - "none": "Nessuno" + "none": "Nessuno", + "view": "Dettaglio" }, "menu": { "dashboard": "Dashboard", @@ -39,6 +40,9 @@ "articles": "Articoli", "resources": "Risorse", "warehouse": "Magazzino", + "purchases": "Acquisti", + "sales": "Vendite", + "production": "Produzione", "reports": "Report", "modules": "Moduli", "autoCodes": "Codici Auto", @@ -571,6 +575,267 @@ } } }, + "purchases": { + "supplier": { + "title": "Fornitori", + "newSupplier": "Nuovo Fornitore", + "createTitle": "Nuovo Fornitore", + "editTitle": "Modifica Fornitore", + "columns": { + "code": "Codice", + "name": "Ragione Sociale", + "vatNumber": "P.IVA", + "email": "Email", + "phone": "Telefono", + "city": "Città", + "status": "Stato" + }, + "fields": { + "name": "Ragione Sociale", + "vatNumber": "P.IVA", + "fiscalCode": "Codice Fiscale", + "address": "Indirizzo", + "city": "Città", + "province": "Provincia", + "zipCode": "CAP", + "country": "Nazione", + "email": "Email", + "pec": "PEC", + "phone": "Telefono", + "website": "Sito Web", + "paymentTerms": "Termini di Pagamento", + "notes": "Note" + } + }, + "order": { + "title": "Ordini Acquisto", + "newOrder": "Nuovo Ordine", + "createTitle": "Nuovo Ordine", + "editTitle": "Modifica Ordine", + "status": { + "Draft": "Bozza", + "Confirmed": "Confermato", + "PartiallyReceived": "Parz. Ricevuto", + "Received": "Ricevuto", + "Cancelled": "Annullato" + }, + "columns": { + "number": "Numero", + "date": "Data", + "supplier": "Fornitore", + "status": "Stato", + "total": "Totale" + }, + "fields": { + "date": "Data Ordine", + "expectedDate": "Data Prevista Consegna", + "supplier": "Fornitore", + "warehouse": "Magazzino Destinazione", + "notes": "Note" + }, + "lines": { + "article": "Articolo", + "quantity": "Quantità", + "price": "Prezzo Unit.", + "discount": "Sconto %", + "tax": "IVA %", + "total": "Totale" + }, + "total": "Totale Ordine", + "actions": { + "confirm": "Conferma Ordine", + "receive": "Ricevi Merce" + } + } + }, + "sales": { + "order": { + "title": "Ordini Vendita", + "newOrder": "Nuovo Ordine", + "createTitle": "Nuovo Ordine", + "editTitle": "Modifica Ordine", + "status": { + "Draft": "Bozza", + "Confirmed": "Confermato", + "PartiallyShipped": "Parz. Spedito", + "Shipped": "Spedito", + "Invoiced": "Fatturato", + "Cancelled": "Annullato" + }, + "columns": { + "number": "Numero", + "date": "Data", + "customer": "Cliente", + "status": "Stato", + "total": "Totale" + }, + "fields": { + "orderDate": "Data Ordine", + "expectedDeliveryDate": "Data Prevista Consegna", + "customer": "Cliente", + "notes": "Note", + "lineTotal": "Totale Riga", + "article": "Articolo", + "quantity": "Quantità", + "unitPrice": "Prezzo Unit.", + "discount": "Sconto %", + "taxRate": "IVA %" + }, + "totals": { + "gross": "Totale Lordo" + }, + "actions": { + "confirm": "Conferma Ordine", + "ship": "Spedisci Merce" + } + } + }, + "production": { + "bom": { + "title": "Distinte Base", + "newBom": "Nuova Distinta Base", + "createTitle": "Nuova Distinta Base", + "editTitle": "Modifica Distinta Base", + "fields": { + "name": "Nome", + "description": "Descrizione", + "article": "Articolo Prodotto", + "quantity": "Quantità Prodotta", + "components": "Componenti", + "componentArticle": "Articolo Componente", + "componentQuantity": "Quantità", + "scrapPercentage": "Scarto %", + "noComponents": "Nessun componente aggiunto" + }, + "columns": { + "name": "Nome", + "article": "Articolo", + "quantity": "Quantità" + } + }, + "dashboard": { + "title": "Dashboard Produzione", + "activeOrders": "Ordini Attivi", + "lateOrders": "Ordini in Ritardo", + "mrpSuggestions": "Suggerimenti MRP", + "completedToday": "Completati Oggi", + "recentOrders": "Ordini Recenti" + }, + "order": { + "title": "Ordini di Produzione", + "newOrder": "Nuovo Ordine", + "createTitle": "Nuovo Ordine", + "editTitle": "Modifica Ordine", + "subOrders": "Ordini Figli", + "noSubOrders": "Nessun ordine figlio presente", + "status": { + "Draft": "Bozza", + "Planned": "Pianificato", + "Released": "Rilasciato", + "InProgress": "In Corso", + "Completed": "Completato", + "Cancelled": "Annullato" + }, + "columns": { + "code": "Codice", + "startDate": "Data Inizio", + "article": "Articolo", + "parentOrder": "Ordine Padre", + "quantity": "Quantità", + "status": "Stato" + }, + "fields": { + "article": "Articolo da Produrre", + "quantity": "Quantità", + "startDate": "Data Inizio", + "dueDate": "Data Scadenza", + "notes": "Note", + "bom": "Distinta Base", + "bomHelp": "Seleziona una DiBa per precompilare i componenti", + "createChildOrders": "Crea ordini figli per semilavorati automaticamente" + }, + "actions": { + "plan": "Pianifica", + "release": "Rilascia", + "start": "Avvia", + "complete": "Completa" + }, + "phases": { + "title": "Fasi di Produzione", + "sequence": "Seq", + "name": "Fase", + "workCenter": "Centro di Lavoro", + "status": "Stato", + "progress": "Avanzamento", + "actions": "Azioni", + "start": "Avvia Fase", + "complete": "Completa Fase", + "quantity": "Qta Completata", + "scrapped": "Qta Scartata", + "duration": "Durata (min)", + "durationHelp": "Stimata: {{estimated}} min", + "statusValue": { + "Pending": "In Attesa", + "InProgress": "In Corso", + "Completed": "Completata", + "Paused": "In Pausa" + } + } + }, + "workCenter": { + "title": "Centri di Lavoro", + "new": "Nuovo Centro", + "edit": "Modifica Centro", + "fields": { + "code": "Codice", + "name": "Nome", + "description": "Descrizione", + "costPerHour": "Costo Orario" + } + }, + "cycle": { + "title": "Cicli Produttivi", + "new": "Nuovo Ciclo", + "createTitle": "Nuovo Ciclo Produttivo", + "editTitle": "Modifica Ciclo Produttivo", + "phases": "Fasi del Ciclo", + "addPhase": "Aggiungi Fase", + "noPhases": "Nessuna fase definita", + "fields": { + "name": "Nome Ciclo", + "description": "Descrizione", + "article": "Articolo", + "isDefault": "Predefinito", + "phaseName": "Nome Fase", + "workCenter": "Centro di Lavoro", + "duration": "Durata Unit. (min)", + "setupTime": "Tempo Setup (min)" + } + }, + "mrp": { + "title": "Pianificazione MRP", + "run": "Esegui MRP", + "configurationTitle": "Configurazione MRP", + "configurationDescription": "Seleziona le opzioni per l'esecuzione del calcolo MRP.", + "includeSafetyStock": "Includi Scorta di Sicurezza", + "includeSalesOrders": "Includi Ordini di Vendita", + "includeForecasts": "Includi Previsioni", + "columns": { + "date": "Data Calcolo", + "article": "Articolo", + "type": "Tipo", + "quantity": "Quantità", + "reason": "Motivo" + }, + "type": { + "production": "Produzione", + "purchase": "Acquisto" + }, + "actions": { + "process": "Processa / Crea Ordine" + } + } + }, "warehouse": { "dashboard": { "newInbound": "Nuovo Carico", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 77e25df..0b3e258 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -21,6 +21,9 @@ import ModulePurchasePage from "./pages/ModulePurchasePage"; import AutoCodesAdminPage from "./pages/AutoCodesAdminPage"; import CustomFieldsAdminPage from "./pages/CustomFieldsAdminPage"; import WarehouseRoutes from "./modules/warehouse/routes"; +import PurchasesRoutes from "./modules/purchases/routes"; +import SalesRoutes from "./modules/sales/routes"; +import ProductionRoutes from "./modules/production/routes"; import { ModuleGuard } from "./components/ModuleGuard"; import { useRealTimeUpdates } from "./hooks/useRealTimeUpdates"; import { CollaborationProvider } from "./contexts/CollaborationContext"; @@ -99,6 +102,33 @@ function App() { } /> + {/* Purchases Module */} + + + + } + /> + {/* Sales Module */} + + + + } + /> + {/* Production Module */} + + + + } + /> diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index df61b16..9339874 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -30,6 +30,9 @@ import { Extension as ModulesIcon, Warehouse as WarehouseIcon, Code as AutoCodeIcon, + ShoppingCart as ShoppingCartIcon, + Sell as SellIcon, + Factory as ProductionIcon, } from "@mui/icons-material"; import CollaborationIndicator from "./collaboration/CollaborationIndicator"; import { useModules } from "../contexts/ModuleContext"; @@ -68,6 +71,24 @@ export default function Layout() { path: "/warehouse", moduleCode: "warehouse", }, + { + text: t('menu.purchases'), + icon: , + path: "/purchases/orders", + moduleCode: "purchases", + }, + { + text: t('menu.sales'), + icon: , + path: "/sales/orders", + moduleCode: "sales", + }, + { + text: t('menu.production'), + icon: , + path: "/production/orders", + moduleCode: "production", + }, { text: t('menu.reports'), icon: , path: "/report-templates" }, { text: t('menu.modules'), icon: , path: "/modules" }, { text: t('menu.autoCodes'), icon: , path: "/admin/auto-codes" }, diff --git a/frontend/src/modules/production/components/MrpConfigurationDialog.tsx b/frontend/src/modules/production/components/MrpConfigurationDialog.tsx new file mode 100644 index 0000000..712badc --- /dev/null +++ b/frontend/src/modules/production/components/MrpConfigurationDialog.tsx @@ -0,0 +1,90 @@ +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + FormControlLabel, + Switch, + Box, + Typography +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { MrpConfigurationDto } from '../types'; + +interface MrpConfigurationDialogProps { + open: boolean; + onClose: () => void; + onRun: (config: MrpConfigurationDto) => void; + isLoading: boolean; +} + +export default function MrpConfigurationDialog({ open, onClose, onRun, isLoading }: MrpConfigurationDialogProps) { + const { t } = useTranslation(); + const [config, setConfig] = useState({ + includeSafetyStock: true, + includeSalesOrders: true, + includeForecasts: false, + warehouseIds: [] // Empty means all + }); + + const handleChange = (field: keyof MrpConfigurationDto) => (event: React.ChangeEvent) => { + setConfig({ ...config, [field]: event.target.checked }); + }; + + const handleRun = () => { + onRun(config); + }; + + return ( + + {t('production.mrp.configurationTitle')} + + + + {t('production.mrp.configurationDescription')} + + + + } + label={t('production.mrp.includeSafetyStock')} + /> + + + } + label={t('production.mrp.includeSalesOrders')} + /> + + + } + label={t('production.mrp.includeForecasts')} + /> + + + + + + + + ); +} diff --git a/frontend/src/modules/production/components/ProductionLayout.tsx b/frontend/src/modules/production/components/ProductionLayout.tsx new file mode 100644 index 0000000..3abd422 --- /dev/null +++ b/frontend/src/modules/production/components/ProductionLayout.tsx @@ -0,0 +1,47 @@ +import { Outlet, useLocation, useNavigate } from "react-router-dom"; +import { Box, Paper, Tab, Tabs } from "@mui/material"; +import { useTranslation } from "react-i18next"; + +export default function ProductionLayout() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const location = useLocation(); + + // Determine active tab based on current path + const getActiveTab = () => { + const path = location.pathname; + if (path.includes("/production/bom")) return "/production/bom"; + if (path.includes("/production/work-centers")) return "/production/work-centers"; + if (path.includes("/production/cycles")) return "/production/cycles"; + if (path.includes("/production/mrp")) return "/production/mrp"; + return "/production/orders"; + }; + + const handleChange = (_event: React.SyntheticEvent, newValue: string) => { + navigate(newValue); + }; + + return ( + + + + + + + + + + + + + + + ); +} diff --git a/frontend/src/modules/production/components/ProductionOrderPhases.tsx b/frontend/src/modules/production/components/ProductionOrderPhases.tsx new file mode 100644 index 0000000..1cdd1a3 --- /dev/null +++ b/frontend/src/modules/production/components/ProductionOrderPhases.tsx @@ -0,0 +1,240 @@ +import { useState } from "react"; +import { + Box, + Typography, + Button, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Chip, + IconButton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, +} from "@mui/material"; +import { + PlayArrow as StartIcon, + Check as CompleteIcon, + Edit as EditIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { productionService } from "../services/productionService"; +import { + ProductionOrderDto, + ProductionOrderPhaseDto, + ProductionPhaseStatus, + UpdateProductionOrderPhaseDto, +} from "../types"; + +interface Props { + order: ProductionOrderDto; + isReadOnly?: boolean; +} + +export default function ProductionOrderPhases({ order, isReadOnly }: Props) { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const [editingPhase, setEditingPhase] = useState(null); + const [quantityCompleted, setQuantityCompleted] = useState(0); + const [quantityScrapped, setQuantityScrapped] = useState(0); + const [actualDuration, setActualDuration] = useState(0); + + const updatePhaseMutation = useMutation({ + mutationFn: ({ + phaseId, + data, + }: { + phaseId: number; + data: UpdateProductionOrderPhaseDto; + }) => productionService.updateProductionOrderPhase(order.id, phaseId, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["production-order", order.id.toString()] }); + handleClose(); + }, + }); + + const handleStartPhase = (phase: ProductionOrderPhaseDto) => { + updatePhaseMutation.mutate({ + phaseId: phase.id, + data: { + status: ProductionPhaseStatus.InProgress, + quantityCompleted: phase.quantityCompleted, + quantityScrapped: phase.quantityScrapped, + actualDurationMinutes: phase.actualDurationMinutes, + }, + }); + }; + + const handleCompletePhase = (phase: ProductionOrderPhaseDto) => { + setEditingPhase(phase); + setQuantityCompleted(phase.quantityCompleted); + setQuantityScrapped(phase.quantityScrapped); + setActualDuration(phase.actualDurationMinutes || phase.estimatedDurationMinutes); + }; + + const handleClose = () => { + setEditingPhase(null); + }; + + const handleSubmitCompletion = () => { + if (!editingPhase) return; + + updatePhaseMutation.mutate({ + phaseId: editingPhase.id, + data: { + status: ProductionPhaseStatus.Completed, + quantityCompleted: Number(quantityCompleted), + quantityScrapped: Number(quantityScrapped), + actualDurationMinutes: Number(actualDuration), + }, + }); + }; + + const getStatusColor = (status: ProductionPhaseStatus) => { + switch (status) { + case ProductionPhaseStatus.Pending: + return "default"; + case ProductionPhaseStatus.InProgress: + return "warning"; + case ProductionPhaseStatus.Completed: + return "success"; + case ProductionPhaseStatus.Paused: + return "error"; + default: + return "default"; + } + }; + + + + return ( + + + {t("production.order.phases.title")} + + + + + + {t("production.order.phases.sequence")} + {t("production.order.phases.name")} + {t("production.order.phases.workCenter")} + {t("production.order.phases.status")} + {t("production.order.phases.quantity")} + {t("production.order.phases.scrapped")} + {t("production.order.phases.duration")} + {t("production.order.phases.actions")} + + + + {order.phases?.map((phase) => ( + + {phase.sequence} + {phase.name} + {phase.workCenterName} + + + + {phase.quantityCompleted} + {phase.quantityScrapped} + + {phase.actualDurationMinutes} / {phase.estimatedDurationMinutes} + + + {!isReadOnly && ( + + {phase.status === ProductionPhaseStatus.Pending && ( + handleStartPhase(phase)} + title={t("production.order.phases.start")} + > + + + )} + {(phase.status === ProductionPhaseStatus.InProgress || + phase.status === ProductionPhaseStatus.Pending) && ( + handleCompletePhase(phase)} + title={t("production.order.phases.complete")} + > + + + )} + {phase.status === ProductionPhaseStatus.Completed && ( + handleCompletePhase(phase)} // Re-open dialog to edit + title={t("common.edit")} + > + + + )} + + )} + + + ))} + {(!order.phases || order.phases.length === 0) && ( + + + {t("production.cycle.noPhases")} + + + )} + +
+
+ + + {t("production.order.phases.complete")} + + + setQuantityCompleted(Number(e.target.value))} + fullWidth + /> + setQuantityScrapped(Number(e.target.value))} + fullWidth + /> + setActualDuration(Number(e.target.value))} + fullWidth + helperText={t("production.order.phases.durationHelp", { estimated: editingPhase?.estimatedDurationMinutes })} + /> + + + + + + + +
+ ); +} diff --git a/frontend/src/modules/production/pages/BillOfMaterialsFormPage.tsx b/frontend/src/modules/production/pages/BillOfMaterialsFormPage.tsx new file mode 100644 index 0000000..f7c8ab2 --- /dev/null +++ b/frontend/src/modules/production/pages/BillOfMaterialsFormPage.tsx @@ -0,0 +1,352 @@ +import { useEffect } from "react"; +import { useForm, Controller, useFieldArray } from "react-hook-form"; +import { useNavigate, useParams } from "react-router-dom"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Button, + Paper, + Typography, + Grid, + TextField, + Autocomplete, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + IconButton, + Alert, + FormControlLabel, + Switch, +} from "@mui/material"; +import { + Save as SaveIcon, + ArrowBack as BackIcon, + Add as AddIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import { articleService } from "../../warehouse/services/warehouseService"; +import { CreateBillOfMaterialsDto, UpdateBillOfMaterialsDto } from "../types"; + +export default function BillOfMaterialsFormPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { id } = useParams(); + const isEdit = Boolean(id); + const queryClient = useQueryClient(); + + const { control, handleSubmit, reset, setValue, formState: { errors } } = useForm({ + defaultValues: { + name: "", + description: "", + articleId: 0, + quantity: 1, + isActive: true, + components: [], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "components", + }); + + const { data: bom, isLoading } = useQuery({ + queryKey: ["bom", id], + queryFn: () => productionService.getBillOfMaterialsById(Number(id)), + enabled: isEdit, + }); + + const { data: articles = [] } = useQuery({ + queryKey: ["articles"], + queryFn: () => articleService.getAll(), + }); + + useEffect(() => { + if (bom) { + reset({ + name: bom.name, + description: bom.description, + articleId: bom.articleId, + quantity: bom.quantity, + isActive: bom.isActive, + components: bom.components.map(c => ({ + componentArticleId: c.componentArticleId, + quantity: c.quantity, + scrapPercentage: c.scrapPercentage, + id: c.id // Keep ID for updates + })), + }); + } + }, [bom, reset]); + + const createMutation = useMutation({ + mutationFn: (data: CreateBillOfMaterialsDto) => productionService.createBillOfMaterials(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["boms"] }); + navigate("/production/bom"); + }, + }); + + const updateMutation = useMutation({ + mutationFn: (data: UpdateBillOfMaterialsDto) => productionService.updateBillOfMaterials(Number(id), data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["boms"] }); + queryClient.invalidateQueries({ queryKey: ["bom", id] }); + navigate("/production/bom"); + }, + }); + + const onSubmit = (data: any) => { + if (isEdit) { + const updateData: UpdateBillOfMaterialsDto = { + name: data.name, + description: data.description, + quantity: data.quantity, + isActive: data.isActive, + components: data.components.map((c: any) => ({ + id: c.id, + componentArticleId: c.componentArticleId, + quantity: c.quantity, + scrapPercentage: c.scrapPercentage, + isDeleted: false + })) + }; + updateMutation.mutate(updateData); + } else { + createMutation.mutate(data); + } + }; + + const handleComponentArticleChange = (index: number, articleId: number | null) => { + if (!articleId) return; + setValue(`components.${index}.componentArticleId`, articleId); + }; + + if (isEdit && isLoading) return Loading...; + + return ( + + + + + + {isEdit ? `${t("production.bom.editTitle")} ${bom?.name}` : t("production.bom.createTitle")} + + + + + + + {(createMutation.isError || updateMutation.isError) && ( + + {t("common.error")} + + )} + + + + + ( + + )} + /> + + + + ( + `${option.code} - ${option.description}`} + value={articles.find(a => a.id === field.value) || null} + onChange={(_, newValue) => field.onChange(newValue?.id)} + disabled={isEdit} // Cannot change article of existing BOM + renderInput={(params) => ( + + )} + /> + )} + /> + + + + ( + field.onChange(Number(e.target.value))} + error={!!errors.quantity} + helperText={errors.quantity?.message} + /> + )} + /> + + + + ( + + )} + /> + + + {isEdit && ( + + ( + } + label={t("common.active")} + /> + )} + /> + + )} + + + + + + {t("production.bom.fields.components")} + + + + + + + + {t("production.bom.fields.componentArticle")} + {t("production.bom.fields.componentQuantity")} + {t("production.bom.fields.scrapPercentage")} + + + + + {fields.map((field: any, index: number) => ( + + + ( + `${option.code} - ${option.description}`} + value={articles.find(a => a.id === articleField.value) || null} + onChange={(_, newValue) => handleComponentArticleChange(index, newValue?.id || null)} + renderInput={(params) => ( + + )} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + remove(index)}> + + + + + ))} + {fields.length === 0 && ( + + + {t("production.bom.noComponents")} + + + )} + +
+
+
+
+ ); +} diff --git a/frontend/src/modules/production/pages/BillOfMaterialsPage.tsx b/frontend/src/modules/production/pages/BillOfMaterialsPage.tsx new file mode 100644 index 0000000..13eb776 --- /dev/null +++ b/frontend/src/modules/production/pages/BillOfMaterialsPage.tsx @@ -0,0 +1,123 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { + Box, + Typography, + Button, + Paper, + IconButton, + Tooltip, +} from "@mui/material"; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridToolbar, +} from "@mui/x-data-grid"; +import { + Add as AddIcon, + Visibility as ViewIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import { BillOfMaterialsDto } from "../types"; + +export default function BillOfMaterialsPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const { data: boms = [], isLoading } = useQuery({ + queryKey: ["boms"], + queryFn: () => productionService.getBillOfMaterials(), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => productionService.deleteBillOfMaterials(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["boms"] }); + }, + }); + + const handleCreate = () => { + navigate("/production/bom/new"); + }; + + const handleView = (id: number) => { + navigate(`/production/bom/${id}`); + }; + + const handleDelete = (id: number) => { + if (confirm(t("common.confirmDelete"))) { + deleteMutation.mutate(id); + } + }; + + const columns: GridColDef[] = [ + { field: "name", headerName: t("production.bom.columns.name"), flex: 1, minWidth: 200 }, + { field: "articleName", headerName: t("production.bom.columns.article"), flex: 1, minWidth: 200 }, + { field: "quantity", headerName: t("production.bom.columns.quantity"), width: 100, type: 'number' }, + { + field: "actions", + headerName: t("common.actions"), + width: 120, + sortable: false, + renderCell: (params: GridRenderCellParams) => ( + + + handleView(params.row.id)}> + + + + + handleDelete(params.row.id)} + > + + + + + ), + }, + ]; + + return ( + + + {t("production.bom.title")} + + + + + + + + ); +} diff --git a/frontend/src/modules/production/pages/MrpPage.tsx b/frontend/src/modules/production/pages/MrpPage.tsx new file mode 100644 index 0000000..904ca03 --- /dev/null +++ b/frontend/src/modules/production/pages/MrpPage.tsx @@ -0,0 +1,154 @@ +import { useState } from "react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Typography, + Button, + Paper, + IconButton, + Tooltip, + Chip, + CircularProgress, +} from "@mui/material"; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridToolbar, +} from "@mui/x-data-grid"; +import { + PlayArrow as RunIcon, + Check as CheckIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import MrpConfigurationDialog from "../components/MrpConfigurationDialog"; +import { MrpSuggestionDto, MrpSuggestionType, MrpConfigurationDto } from "../types"; +import dayjs from "dayjs"; + +export default function MrpPage() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const [isRunning, setIsRunning] = useState(false); + const [isConfigOpen, setIsConfigOpen] = useState(false); + + const { data: suggestions = [], isLoading } = useQuery({ + queryKey: ["mrpSuggestions"], + queryFn: () => productionService.getMrpSuggestions(false), + }); + + const runMutation = useMutation({ + mutationFn: (config: MrpConfigurationDto) => productionService.runMrp(config), + onMutate: () => { + setIsRunning(true); + setIsConfigOpen(false); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["mrpSuggestions"] }); + setIsRunning(false); + }, + onError: () => setIsRunning(false), + }); + + const processMutation = useMutation({ + mutationFn: (id: number) => productionService.processMrpSuggestion(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["mrpSuggestions"] }); + }, + }); + + const handleRunClick = () => { + setIsConfigOpen(true); + }; + + const handleConfirmRun = (config: MrpConfigurationDto) => { + runMutation.mutate(config); + }; + + const handleProcess = (id: number) => { + processMutation.mutate(id); + }; + + const columns: GridColDef[] = [ + { + field: "calculationDate", + headerName: t("production.mrp.columns.date"), + width: 150, + valueFormatter: (value: string) => value ? dayjs(value).format('DD/MM/YYYY HH:mm') : '' + }, + { + field: "article", + headerName: t("production.mrp.columns.article"), + flex: 1, + minWidth: 200, + valueGetter: (_value: any, row: MrpSuggestionDto) => row.article?.description || '' + }, + { + field: "type", + headerName: t("production.mrp.columns.type"), + width: 120, + renderCell: (params: GridRenderCellParams) => ( + + ) + }, + { field: "quantity", headerName: t("production.mrp.columns.quantity"), width: 120, type: 'number' }, + { field: "reason", headerName: t("production.mrp.columns.reason"), flex: 1, minWidth: 200 }, + { + field: "actions", + headerName: t("common.actions"), + width: 120, + sortable: false, + renderCell: (params: GridRenderCellParams) => ( + + + handleProcess(params.row.id)} + > + + + + + ), + }, + ]; + + return ( + + + {t("production.mrp.title")} + + + + + + + + setIsConfigOpen(false)} + onRun={handleConfirmRun} + isLoading={isRunning} + /> + + ); +} diff --git a/frontend/src/modules/production/pages/ProductionCycleFormPage.tsx b/frontend/src/modules/production/pages/ProductionCycleFormPage.tsx new file mode 100644 index 0000000..e54b447 --- /dev/null +++ b/frontend/src/modules/production/pages/ProductionCycleFormPage.tsx @@ -0,0 +1,362 @@ +import { useEffect } from "react"; +import { useForm, useFieldArray, Controller } from "react-hook-form"; +import { useNavigate, useParams } from "react-router-dom"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Typography, + Button, + Paper, + Grid, + TextField, + MenuItem, + IconButton, + FormControlLabel, + Switch, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, +} from "@mui/material"; +import { + Save as SaveIcon, + ArrowBack as BackIcon, + Add as AddIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import warehouseService from "../../warehouse/services/warehouseService"; +import { CreateProductionCycleDto, UpdateProductionCycleDto } from "../types"; + +export default function ProductionCycleFormPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { id } = useParams(); + const isEdit = !!id; + const queryClient = useQueryClient(); + + const { control, handleSubmit, reset } = useForm({ + defaultValues: { + name: "", + description: "", + articleId: "", + isDefault: false, + isActive: true, + phases: [], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "phases", + }); + + const { data: articles = [] } = useQuery({ + queryKey: ["articles"], + queryFn: () => warehouseService.articles.getAll(), + }); + + const { data: workCenters = [] } = useQuery({ + queryKey: ["workCenters"], + queryFn: () => productionService.getWorkCenters(), + }); + + const { data: cycle } = useQuery({ + queryKey: ["productionCycle", id], + queryFn: () => productionService.getProductionCycleById(Number(id)), + enabled: isEdit, + }); + + useEffect(() => { + if (cycle) { + reset({ + name: cycle.name, + description: cycle.description, + articleId: cycle.articleId, + isDefault: cycle.isDefault, + isActive: cycle.isActive, + phases: cycle.phases.map((p) => ({ + ...p, + isDeleted: false, + })), + }); + } + }, [cycle, reset]); + + const createMutation = useMutation({ + mutationFn: (data: CreateProductionCycleDto) => + productionService.createProductionCycle(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["productionCycles"] }); + navigate("/production/cycles"); + }, + }); + + const updateMutation = useMutation({ + mutationFn: (data: UpdateProductionCycleDto) => + productionService.updateProductionCycle(Number(id), data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["productionCycles"] }); + navigate("/production/cycles"); + }, + }); + + const onSubmit = (data: any) => { + const formattedPhases = data.phases.map((p: any, index: number) => ({ + id: p.id, + sequence: (index + 1) * 10, // Auto-sequence + name: p.name, + description: p.description, + workCenterId: p.workCenterId, + durationPerUnitMinutes: Number(p.durationPerUnitMinutes), + setupTimeMinutes: Number(p.setupTimeMinutes), + isDeleted: p.isDeleted || false, + })); + + if (isEdit) { + updateMutation.mutate({ + name: data.name, + description: data.description, + isDefault: data.isDefault, + isActive: data.isActive, + phases: formattedPhases, + }); + } else { + createMutation.mutate({ + name: data.name, + description: data.description, + articleId: Number(data.articleId), + isDefault: data.isDefault, + phases: formattedPhases, + }); + } + }; + + return ( + + + navigate("/production/cycles")} sx={{ mr: 2 }}> + + + + {isEdit + ? t("production.cycle.editTitle") + : t("production.cycle.createTitle")} + + + +
+ + + + ( + + )} + /> + + + ( + + {articles.map((article: any) => ( + + {article.code} - {article.description} + + ))} + + )} + /> + + + ( + + )} + /> + + + ( + } + label={t("production.cycle.fields.isDefault")} + /> + )} + /> + + {isEdit && ( + + ( + } + label={t("common.active")} + /> + )} + /> + + )} + + + + + + {t("production.cycle.phases")} + + + + + + + + {t("production.cycle.fields.phaseName")} + {t("production.cycle.fields.workCenter")} + {t("production.cycle.fields.duration")} + {t("production.cycle.fields.setupTime")} + + + + + {fields.map((field, index) => ( + + + ( + + )} + /> + + + ( + + {workCenters.map((wc) => ( + + {wc.name} + + ))} + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + remove(index)} + > + + + + + ))} + {fields.length === 0 && ( + + + {t("production.cycle.noPhases")} + + + )} + +
+
+
+ + + + + +
+
+ ); +} diff --git a/frontend/src/modules/production/pages/ProductionCyclesPage.tsx b/frontend/src/modules/production/pages/ProductionCyclesPage.tsx new file mode 100644 index 0000000..6944ae7 --- /dev/null +++ b/frontend/src/modules/production/pages/ProductionCyclesPage.tsx @@ -0,0 +1,119 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { + Box, + Typography, + Button, + Paper, + IconButton, + Tooltip, +} from "@mui/material"; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridToolbar, +} from "@mui/x-data-grid"; +import { + Add as AddIcon, + Edit as EditIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import { ProductionCycleDto } from "../types"; + +export default function ProductionCyclesPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const { data: cycles = [], isLoading } = useQuery({ + queryKey: ["productionCycles"], + queryFn: () => productionService.getProductionCycles(), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => productionService.deleteProductionCycle(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["productionCycles"] }); + }, + }); + + const handleCreate = () => { + navigate("/production/cycles/new"); + }; + + const handleEdit = (id: number) => { + navigate(`/production/cycles/${id}`); + }; + + const handleDelete = (id: number) => { + if (confirm(t("common.confirmDelete"))) { + deleteMutation.mutate(id); + } + }; + + const columns: GridColDef[] = [ + { field: "name", headerName: t("production.cycle.fields.name"), flex: 1, minWidth: 200 }, + { field: "articleName", headerName: t("production.cycle.fields.article"), flex: 1, minWidth: 200 }, + { + field: "isDefault", + headerName: t("production.cycle.fields.isDefault"), + width: 100, + type: 'boolean' + }, + { + field: "isActive", + headerName: t("common.active"), + width: 100, + type: 'boolean' + }, + { + field: "actions", + headerName: t("common.actions"), + width: 120, + sortable: false, + renderCell: (params: GridRenderCellParams) => ( + + + handleEdit(params.row.id)}> + + + + + handleDelete(params.row.id)} + > + + + + + ), + }, + ]; + + return ( + + + {t("production.cycle.title")} + + + + + + + + ); +} diff --git a/frontend/src/modules/production/pages/ProductionDashboardPage.tsx b/frontend/src/modules/production/pages/ProductionDashboardPage.tsx new file mode 100644 index 0000000..38dcae5 --- /dev/null +++ b/frontend/src/modules/production/pages/ProductionDashboardPage.tsx @@ -0,0 +1,131 @@ +import { useQuery } from "@tanstack/react-query"; +import { Box, Grid, Paper, Typography, Card, CardContent, LinearProgress } from "@mui/material"; +import { + Assignment as OrderIcon, + Warning as AlertIcon, + PrecisionManufacturing as MrpIcon, + CheckCircle as CompletedIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import { ProductionOrderStatus } from "../types"; +import dayjs from "dayjs"; + +export default function ProductionDashboardPage() { + const { t } = useTranslation(); + + const { data: orders = [] } = useQuery({ + queryKey: ["production-orders"], + queryFn: () => productionService.getProductionOrders(), + }); + + const { data: mrpSuggestions = [] } = useQuery({ + queryKey: ["mrpSuggestions"], + queryFn: () => productionService.getMrpSuggestions(false), + }); + + // KPI Calculations + const activeOrders = orders.filter( + (o) => + o.status === ProductionOrderStatus.Released || + o.status === ProductionOrderStatus.InProgress + ).length; + + const lateOrders = orders.filter( + (o) => + dayjs(o.dueDate).isBefore(dayjs()) && + o.status !== ProductionOrderStatus.Completed && + o.status !== ProductionOrderStatus.Cancelled + ).length; + + const completedToday = orders.filter( + (o) => + o.status === ProductionOrderStatus.Completed && + dayjs(o.endDate).isSame(dayjs(), "day") + ).length; + + const pendingSuggestions = mrpSuggestions.filter((s) => !s.isProcessed).length; + + const StatCard = ({ title, value, icon, color }: any) => ( + + + + + {title} + + {icon} + + {value} + + + ); + + return ( + + + {t("production.dashboard.title")} + + + + + } + color="primary" + /> + + + } + color="error" + /> + + + } + color="warning" + /> + + + } + color="success" + /> + + + + + + + {t("production.dashboard.recentOrders")} + {/* Simple list of recent orders */} + {orders.slice(0, 5).map(order => ( + + + {order.code} + {dayjs(order.dueDate).format('DD/MM/YYYY')} + + {order.articleName} + + + ))} + + + + {/* Placeholder for other widgets */} + + + + ); +} diff --git a/frontend/src/modules/production/pages/ProductionOrderFormPage.tsx b/frontend/src/modules/production/pages/ProductionOrderFormPage.tsx new file mode 100644 index 0000000..08ab9ee --- /dev/null +++ b/frontend/src/modules/production/pages/ProductionOrderFormPage.tsx @@ -0,0 +1,439 @@ +import { useState, useEffect } from "react"; +import { useForm, Controller } from "react-hook-form"; +import { useNavigate, useParams } from "react-router-dom"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Button, + Paper, + Typography, + Grid, + TextField, + Autocomplete, + Alert, + Chip, + FormControlLabel, + Checkbox, + Tabs, + Tab, + IconButton, +} from "@mui/material"; +import { DataGrid } from "@mui/x-data-grid"; +import { + Save as SaveIcon, + ArrowBack as BackIcon, + Check as CheckIcon, + PlayArrow as StartIcon, + Done as CompleteIcon, + Visibility as ViewIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import { articleService } from "../../warehouse/services/warehouseService"; +import { CreateProductionOrderDto, UpdateProductionOrderDto, ProductionOrderStatus } from "../types"; +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import dayjs from "dayjs"; +import ProductionOrderPhases from "../components/ProductionOrderPhases"; + +export default function ProductionOrderFormPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { id } = useParams(); + const isEdit = Boolean(id); + const queryClient = useQueryClient(); + const [tabValue, setTabValue] = useState(0); + + const { control, handleSubmit, reset, watch, setValue, formState: { errors } } = useForm({ + defaultValues: { + articleId: 0, + quantity: 1, + startDate: new Date().toISOString(), + dueDate: new Date().toISOString(), + notes: "", + billOfMaterialsId: undefined, + }, + }); + + const { data: order, isLoading } = useQuery({ + queryKey: ["production-order", id], + queryFn: () => productionService.getProductionOrderById(Number(id)), + enabled: isEdit, + }); + + const { data: articles = [] } = useQuery({ + queryKey: ["articles"], + queryFn: () => articleService.getAll(), + }); + + const { data: boms = [] } = useQuery({ + queryKey: ["boms"], + queryFn: () => productionService.getBillOfMaterials(), + }); + + useEffect(() => { + if (order) { + reset({ + articleId: order.articleId, + quantity: order.quantity, + startDate: order.startDate, + dueDate: order.dueDate, + notes: order.notes, + }); + } + }, [order, reset]); + + const createMutation = useMutation({ + mutationFn: (data: CreateProductionOrderDto) => productionService.createProductionOrder(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["production-orders"] }); + navigate("/production/orders"); + }, + }); + + const updateMutation = useMutation({ + mutationFn: (data: UpdateProductionOrderDto) => productionService.updateProductionOrder(Number(id), data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["production-orders"] }); + queryClient.invalidateQueries({ queryKey: ["production-order", id] }); + navigate("/production/orders"); + }, + }); + + const changeStatusMutation = useMutation({ + mutationFn: (status: ProductionOrderStatus) => productionService.changeProductionOrderStatus(Number(id), status), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["production-orders"] }); + queryClient.invalidateQueries({ queryKey: ["production-order", id] }); + }, + }); + + const onSubmit = (data: CreateProductionOrderDto) => { + if (isEdit) { + // For update we need to pass status as well, but form doesn't have it. + // We assume status doesn't change via form save, only via actions. + const updateData: UpdateProductionOrderDto = { + quantity: data.quantity, + startDate: data.startDate, + dueDate: data.dueDate, + notes: data.notes, + status: order!.status // Keep existing status + }; + updateMutation.mutate(updateData); + } else { + createMutation.mutate(data); + } + }; + + const handleArticleChange = (articleId: number | null) => { + if (!articleId) return; + setValue('articleId', articleId); + + // Try to find a BOM for this article to auto-select + const bom = boms.find(b => b.articleId === articleId); + if (bom) { + setValue('billOfMaterialsId', bom.id); + } + }; + + const isReadOnly = isEdit && order?.status === ProductionOrderStatus.Completed; + + if (isEdit && isLoading) return Loading...; + + return ( + + + + + + + {isEdit ? `${t("production.order.editTitle")} ${order?.code}` : t("production.order.createTitle")} + + {isEdit && order && ( + + )} + + + + + {isEdit && order?.status === ProductionOrderStatus.Draft && ( + + )} + + {isEdit && order?.status === ProductionOrderStatus.Planned && ( + + )} + + {isEdit && order?.status === ProductionOrderStatus.Released && ( + + )} + + {isEdit && order?.status === ProductionOrderStatus.InProgress && ( + + )} + + {!isReadOnly && ( + + )} + + + + {(createMutation.isError || updateMutation.isError || changeStatusMutation.isError) && ( + + {t("common.error")} + + )} + + + + + ( + `${option.code} - ${option.description}`} + value={articles.find(a => a.id === field.value) || null} + onChange={(_, newValue) => handleArticleChange(newValue?.id || null)} + disabled={isEdit || isReadOnly} // Cannot change article after creation + renderInput={(params) => ( + + )} + /> + )} + /> + + + {!isEdit && ( + + ( + b.articleId === watch('articleId'))} + getOptionLabel={(option) => option.name} + value={boms.find(b => b.id === field.value) || null} + onChange={(_, newValue) => field.onChange(newValue?.id)} + disabled={isReadOnly} + renderInput={(params) => ( + + )} + /> + )} + /> + + )} + + {!isEdit && ( + + ( + field.onChange(e.target.checked)} + /> + } + label={t("production.order.fields.createChildOrders")} + /> + )} + /> + + )} + + + ( + field.onChange(Number(e.target.value))} + error={!!errors.quantity} + helperText={errors.quantity?.message} + /> + )} + /> + + + + ( + field.onChange(date?.toISOString())} + disabled={isReadOnly} + slotProps={{ textField: { fullWidth: true } }} + /> + )} + /> + + + + ( + field.onChange(date?.toISOString())} + disabled={isReadOnly} + slotProps={{ textField: { fullWidth: true } }} + /> + )} + /> + + + + ( + + )} + /> + + + + + {isEdit && order && ( + + + setTabValue(val)}> + + + + + + + + + {order.childOrders && order.childOrders.length > 0 ? ( + + t(`production.order.status.${ProductionOrderStatus[row.status]}`) }, + { + field: "actions", + headerName: t("common.actions"), + width: 100, + renderCell: (params: any) => ( + navigate(`/production/orders/${params.row.id}`)}> + + + ) + } + ]} + hideFooter + autoHeight + /> + + ) : ( + {t("production.order.noSubOrders")} + )} + + + )} + + ); +} + +function TabPanel(props: { children?: React.ReactNode; index: number; value: number }) { + const { children, value, index, ...other } = props; + + return ( + + ); +} diff --git a/frontend/src/modules/production/pages/ProductionOrdersPage.tsx b/frontend/src/modules/production/pages/ProductionOrdersPage.tsx new file mode 100644 index 0000000..dece6fd --- /dev/null +++ b/frontend/src/modules/production/pages/ProductionOrdersPage.tsx @@ -0,0 +1,167 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { + Box, + Typography, + Button, + Paper, + Chip, + IconButton, + Tooltip, +} from "@mui/material"; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridToolbar, +} from "@mui/x-data-grid"; +import { + Add as AddIcon, + Visibility as ViewIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import { ProductionOrderDto, ProductionOrderStatus } from "../types"; +import dayjs from "dayjs"; + +export default function ProductionOrdersPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const { data: orders = [], isLoading } = useQuery({ + queryKey: ["production-orders"], + queryFn: () => productionService.getProductionOrders(), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => productionService.deleteProductionOrder(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["production-orders"] }); + }, + }); + + const handleCreate = () => { + navigate("/production/orders/new"); + }; + + const handleView = (id: number) => { + navigate(`/production/orders/${id}`); + }; + + const handleDelete = (id: number) => { + if (confirm(t("common.confirmDelete"))) { + deleteMutation.mutate(id); + } + }; + + const getStatusChip = (status: ProductionOrderStatus) => { + const label = t(`production.order.status.${ProductionOrderStatus[status]}`); + switch (status) { + case ProductionOrderStatus.Draft: + return ; + case ProductionOrderStatus.Planned: + return ; + case ProductionOrderStatus.Released: + return ; + case ProductionOrderStatus.InProgress: + return ; + case ProductionOrderStatus.Completed: + return ; + case ProductionOrderStatus.Cancelled: + return ; + default: + return ; + } + }; + + const columns: GridColDef[] = [ + { field: "code", headerName: t("production.order.columns.code"), width: 150 }, + { + field: "startDate", + headerName: t("production.order.columns.startDate"), + width: 120, + valueFormatter: (value) => dayjs(value).format("DD/MM/YYYY"), + }, + { field: "articleName", headerName: t("production.order.columns.article"), flex: 1, minWidth: 200 }, + { field: "parentProductionOrderCode", headerName: t("production.order.columns.parentOrder"), width: 150 }, + { field: "quantity", headerName: t("production.order.columns.quantity"), width: 100, type: 'number' }, + { + field: "status", + headerName: t("production.order.columns.status"), + width: 150, + renderCell: (params: GridRenderCellParams) => + getStatusChip(params.row.status), + }, + { + field: "actions", + headerName: t("common.actions"), + width: 120, + sortable: false, + renderCell: (params: GridRenderCellParams) => ( + + + handleView(params.row.id)}> + + + + {(params.row.status === ProductionOrderStatus.Draft) && ( + + handleDelete(params.row.id)} + > + + + + )} + + ), + }, + ]; + + return ( + + + {t("production.order.title")} + + + + + + + + ); +} diff --git a/frontend/src/modules/production/pages/WorkCentersPage.tsx b/frontend/src/modules/production/pages/WorkCentersPage.tsx new file mode 100644 index 0000000..7688345 --- /dev/null +++ b/frontend/src/modules/production/pages/WorkCentersPage.tsx @@ -0,0 +1,252 @@ +import { useState } from "react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Typography, + Button, + Paper, + IconButton, + Tooltip, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + FormControlLabel, + Switch, +} from "@mui/material"; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridToolbar, +} from "@mui/x-data-grid"; +import { + Add as AddIcon, + Edit as EditIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { productionService } from "../services/productionService"; +import { WorkCenterDto, CreateWorkCenterDto, UpdateWorkCenterDto } from "../types"; +import { useForm, Controller } from "react-hook-form"; + +export default function WorkCentersPage() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const [open, setOpen] = useState(false); + const [editingId, setEditingId] = useState(null); + + const { data: workCenters = [], isLoading } = useQuery({ + queryKey: ["workCenters"], + queryFn: () => productionService.getWorkCenters(), + }); + + const { control, handleSubmit, reset, setValue } = useForm(); + + const createMutation = useMutation({ + mutationFn: (data: CreateWorkCenterDto) => productionService.createWorkCenter(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["workCenters"] }); + handleClose(); + }, + }); + + const updateMutation = useMutation({ + mutationFn: ({ id, data }: { id: number; data: UpdateWorkCenterDto }) => + productionService.updateWorkCenter(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["workCenters"] }); + handleClose(); + }, + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => productionService.deleteWorkCenter(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["workCenters"] }); + }, + }); + + const handleOpen = (workCenter?: WorkCenterDto) => { + if (workCenter) { + setEditingId(workCenter.id); + setValue("code", workCenter.code); + setValue("name", workCenter.name); + setValue("description", workCenter.description); + setValue("costPerHour", workCenter.costPerHour); + setValue("isActive", workCenter.isActive); + } else { + setEditingId(null); + reset({ code: "", name: "", description: "", costPerHour: 0, isActive: true }); + } + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + setEditingId(null); + reset(); + }; + + const onSubmit = (data: CreateWorkCenterDto & { isActive: boolean }) => { + if (editingId) { + updateMutation.mutate({ + id: editingId, + data: { + name: data.name, + description: data.description, + costPerHour: data.costPerHour, + isActive: data.isActive, + }, + }); + } else { + createMutation.mutate(data); + } + }; + + const handleDelete = (id: number) => { + if (confirm(t("common.confirmDelete"))) { + deleteMutation.mutate(id); + } + }; + + const columns: GridColDef[] = [ + { field: "code", headerName: t("production.workCenter.fields.code"), width: 150 }, + { field: "name", headerName: t("production.workCenter.fields.name"), flex: 1, minWidth: 200 }, + { field: "costPerHour", headerName: t("production.workCenter.fields.costPerHour"), width: 150, type: 'number' }, + { + field: "isActive", + headerName: t("common.active"), + width: 100, + type: 'boolean' + }, + { + field: "actions", + headerName: t("common.actions"), + width: 120, + sortable: false, + renderCell: (params: GridRenderCellParams) => ( + + + handleOpen(params.row)}> + + + + + handleDelete(params.row.id)} + > + + + + + ), + }, + ]; + + return ( + + + {t("production.workCenter.title")} + + + + + + + + +
+ + {editingId ? t("production.workCenter.edit") : t("production.workCenter.new")} + + + + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + {editingId && ( + ( + } + label={t("common.active")} + /> + )} + /> + )} + + + + + + +
+
+
+ ); +} diff --git a/frontend/src/modules/production/routes.tsx b/frontend/src/modules/production/routes.tsx new file mode 100644 index 0000000..45ab96f --- /dev/null +++ b/frontend/src/modules/production/routes.tsx @@ -0,0 +1,41 @@ +import { Routes, Route } from "react-router-dom"; +import ProductionDashboardPage from "./pages/ProductionDashboardPage"; +import ProductionOrdersPage from "./pages/ProductionOrdersPage"; +import ProductionOrderFormPage from "./pages/ProductionOrderFormPage"; +import BillOfMaterialsPage from "./pages/BillOfMaterialsPage"; +import BillOfMaterialsFormPage from "./pages/BillOfMaterialsFormPage"; +import WorkCentersPage from "./pages/WorkCentersPage"; +import ProductionCyclesPage from "./pages/ProductionCyclesPage"; +import ProductionCycleFormPage from "./pages/ProductionCycleFormPage"; +import MrpPage from "./pages/MrpPage"; +import ProductionLayout from "./components/ProductionLayout"; + +export default function ProductionRoutes() { + return ( + + }> + } /> + {/* Production Orders */} + } /> + } /> + } /> + + {/* Bill of Materials */} + } /> + } /> + } /> + + {/* Work Centers */} + } /> + + {/* Production Cycles */} + } /> + } /> + } /> + + {/* MRP */} + } /> + + + ); +} diff --git a/frontend/src/modules/production/services/productionService.ts b/frontend/src/modules/production/services/productionService.ts new file mode 100644 index 0000000..23da282 --- /dev/null +++ b/frontend/src/modules/production/services/productionService.ts @@ -0,0 +1,150 @@ +import axios from 'axios'; +import { + BillOfMaterialsDto, + CreateBillOfMaterialsDto, + UpdateBillOfMaterialsDto, + ProductionOrderDto, + CreateProductionOrderDto, + UpdateProductionOrderDto, + ProductionOrderStatus, + WorkCenterDto, + CreateWorkCenterDto, + UpdateWorkCenterDto, + ProductionCycleDto, + CreateProductionCycleDto, + UpdateProductionCycleDto, + MrpSuggestionDto, + UpdateProductionOrderPhaseDto, + MrpConfigurationDto +} from '../types'; + +const BOM_API_URL = '/api/production/bom'; +const ORDERS_API_URL = '/api/production/orders'; + +export const productionService = { + // Bill of Materials + getBillOfMaterials: async (): Promise => { + const response = await axios.get(BOM_API_URL); + return response.data; + }, + + getBillOfMaterialsById: async (id: number): Promise => { + const response = await axios.get(`${BOM_API_URL}/${id}`); + return response.data; + }, + + createBillOfMaterials: async (dto: CreateBillOfMaterialsDto): Promise => { + const response = await axios.post(BOM_API_URL, dto); + return response.data; + }, + + updateBillOfMaterials: async (id: number, dto: UpdateBillOfMaterialsDto): Promise => { + const response = await axios.put(`${BOM_API_URL}/${id}`, dto); + return response.data; + }, + + deleteBillOfMaterials: async (id: number): Promise => { + await axios.delete(`${BOM_API_URL}/${id}`); + }, + + // Production Orders + getProductionOrders: async (): Promise => { + const response = await axios.get(ORDERS_API_URL); + return response.data; + }, + + getProductionOrderById: async (id: number): Promise => { + const response = await axios.get(`${ORDERS_API_URL}/${id}`); + return response.data; + }, + + createProductionOrder: async (dto: CreateProductionOrderDto): Promise => { + const response = await axios.post(ORDERS_API_URL, dto); + return response.data; + }, + + updateProductionOrder: async (id: number, dto: UpdateProductionOrderDto): Promise => { + const response = await axios.put(`${ORDERS_API_URL}/${id}`, dto); + return response.data; + }, + + changeProductionOrderStatus: async (id: number, status: ProductionOrderStatus): Promise => { + const response = await axios.put(`${ORDERS_API_URL}/${id}/status`, status, { + headers: { 'Content-Type': 'application/json' } + }); + return response.data; + }, + + updateProductionOrderPhase: async (orderId: number, phaseId: number, dto: UpdateProductionOrderPhaseDto): Promise => { + const response = await axios.put(`${ORDERS_API_URL}/${orderId}/phases/${phaseId}`, dto); + return response.data; + }, + + deleteProductionOrder: async (id: number): Promise => { + await axios.delete(`${ORDERS_API_URL}/${id}`); + }, + + // Work Centers + getWorkCenters: async (): Promise => { + const response = await axios.get('/api/production/work-centers'); + return response.data; + }, + + getWorkCenterById: async (id: number): Promise => { + const response = await axios.get(`/api/production/work-centers/${id}`); + return response.data; + }, + + createWorkCenter: async (dto: CreateWorkCenterDto): Promise => { + const response = await axios.post('/api/production/work-centers', dto); + return response.data; + }, + + updateWorkCenter: async (id: number, dto: UpdateWorkCenterDto): Promise => { + const response = await axios.put(`/api/production/work-centers/${id}`, dto); + return response.data; + }, + + deleteWorkCenter: async (id: number): Promise => { + await axios.delete(`/api/production/work-centers/${id}`); + }, + + // Production Cycles + getProductionCycles: async (): Promise => { + const response = await axios.get('/api/production/cycles'); + return response.data; + }, + + getProductionCycleById: async (id: number): Promise => { + const response = await axios.get(`/api/production/cycles/${id}`); + return response.data; + }, + + createProductionCycle: async (dto: CreateProductionCycleDto): Promise => { + const response = await axios.post('/api/production/cycles', dto); + return response.data; + }, + + updateProductionCycle: async (id: number, dto: UpdateProductionCycleDto): Promise => { + const response = await axios.put(`/api/production/cycles/${id}`, dto); + return response.data; + }, + + deleteProductionCycle: async (id: number): Promise => { + await axios.delete(`/api/production/cycles/${id}`); + }, + + // MRP + runMrp: async (config: MrpConfigurationDto): Promise => { + await axios.post('/api/production/mrp/run', config); + }, + + getMrpSuggestions: async (includeProcessed = false): Promise => { + const response = await axios.get(`/api/production/mrp/suggestions?includeProcessed=${includeProcessed}`); + return response.data; + }, + + processMrpSuggestion: async (id: number): Promise => { + await axios.post(`/api/production/mrp/suggestions/${id}/process`); + } +}; diff --git a/frontend/src/modules/production/types/index.ts b/frontend/src/modules/production/types/index.ts new file mode 100644 index 0000000..8bcce4f --- /dev/null +++ b/frontend/src/modules/production/types/index.ts @@ -0,0 +1,248 @@ +export interface BillOfMaterialsDto { + id: number; + name: string; + description: string; + articleId: number; + articleName: string; + quantity: number; + isActive: boolean; + components: BillOfMaterialsComponentDto[]; +} + +export interface BillOfMaterialsComponentDto { + id: number; + componentArticleId: number; + componentArticleName: string; + quantity: number; + scrapPercentage: number; +} + +export interface CreateBillOfMaterialsDto { + name: string; + description: string; + articleId: number; + quantity: number; + components: CreateBillOfMaterialsComponentDto[]; +} + +export interface CreateBillOfMaterialsComponentDto { + componentArticleId: number; + quantity: number; + scrapPercentage: number; +} + +export interface UpdateBillOfMaterialsDto { + name: string; + description: string; + quantity: number; + isActive: boolean; + components: UpdateBillOfMaterialsComponentDto[]; +} + +export interface UpdateBillOfMaterialsComponentDto { + id?: number; + componentArticleId: number; + quantity: number; + scrapPercentage: number; + isDeleted: boolean; +} + + + +export interface ProductionOrderComponentDto { + id: number; + articleId: number; + articleName: string; + requiredQuantity: number; + consumedQuantity: number; +} + +export interface CreateProductionOrderDto { + articleId: number; + quantity: number; + startDate: string; + dueDate: string; + notes?: string; + billOfMaterialsId?: number; + createChildOrders?: boolean; +} + +export interface UpdateProductionOrderDto { + quantity: number; + startDate: string; + dueDate: string; + notes?: string; + status: ProductionOrderStatus; +} + +export enum ProductionOrderStatus { + Draft = 0, + Planned = 1, + Released = 2, + InProgress = 3, + Completed = 4, + Cancelled = 5 +} + +// Work Centers +export interface WorkCenterDto { + id: number; + code: string; + name: string; + description?: string; + costPerHour: number; + isActive: boolean; +} + +export interface CreateWorkCenterDto { + code: string; + name: string; + description?: string; + costPerHour: number; +} + +export interface UpdateWorkCenterDto { + name: string; + description?: string; + costPerHour: number; + isActive: boolean; +} + +// Production Cycles +export interface ProductionCycleDto { + id: number; + name: string; + description?: string; + articleId: number; + articleName: string; + isDefault: boolean; + isActive: boolean; + phases: ProductionCyclePhaseDto[]; +} + +export interface ProductionCyclePhaseDto { + id: number; + sequence: number; + name: string; + description?: string; + workCenterId: number; + workCenterName: string; + durationPerUnitMinutes: number; + setupTimeMinutes: number; +} + +export interface CreateProductionCycleDto { + name: string; + description?: string; + articleId: number; + isDefault: boolean; + phases: CreateProductionCyclePhaseDto[]; +} + +export interface CreateProductionCyclePhaseDto { + sequence: number; + name: string; + description?: string; + workCenterId: number; + durationPerUnitMinutes: number; + setupTimeMinutes: number; +} + +export interface UpdateProductionCycleDto { + name: string; + description?: string; + isDefault: boolean; + isActive: boolean; + phases: UpdateProductionCyclePhaseDto[]; +} + +export interface UpdateProductionCyclePhaseDto { + id?: number; + sequence: number; + name: string; + description?: string; + workCenterId: number; + durationPerUnitMinutes: number; + setupTimeMinutes: number; + isDeleted: boolean; +} + +// Production Order Phases +export interface ProductionOrderPhaseDto { + id: number; + sequence: number; + name: string; + workCenterId: number; + workCenterName: string; + status: ProductionPhaseStatus; + startDate?: string; + endDate?: string; + quantityCompleted: number; + quantityScrapped: number; + estimatedDurationMinutes: number; + actualDurationMinutes: number; +} + +export enum ProductionPhaseStatus { + Pending = 0, + InProgress = 1, + Completed = 2, + Paused = 3 +} + +export interface UpdateProductionOrderPhaseDto { + status: ProductionPhaseStatus; + quantityCompleted: number; + quantityScrapped: number; + actualDurationMinutes: number; +} + +// Update ProductionOrderDto to include phases +export interface ProductionOrderDto { + id: number; + code: string; + articleId: number; + articleName: string; + quantity: number; + startDate: string; + endDate?: string; + dueDate: string; + status: ProductionOrderStatus; + notes?: string; + components: ProductionOrderComponentDto[]; + phases: ProductionOrderPhaseDto[]; + parentProductionOrderId?: number; + parentProductionOrderCode?: string; + childOrders?: ProductionOrderDto[]; +} + +// MRP +export interface MrpSuggestionDto { + id: number; + calculationDate: string; + articleId: number; + article: { + id: number; + code: string; + description: string; + }; + type: MrpSuggestionType; + quantity: number; + suggestionDate: string; + reason: string; + isProcessed: boolean; +} + +export enum MrpSuggestionType { + Production = 0, + Purchase = 1 +} + +export interface MrpConfigurationDto { + startDate?: string; + endDate?: string; + includeSafetyStock: boolean; + includeSalesOrders: boolean; + includeForecasts: boolean; + warehouseIds?: number[]; +} diff --git a/frontend/src/modules/purchases/pages/PurchaseOrderFormPage.tsx b/frontend/src/modules/purchases/pages/PurchaseOrderFormPage.tsx new file mode 100644 index 0000000..870c13e --- /dev/null +++ b/frontend/src/modules/purchases/pages/PurchaseOrderFormPage.tsx @@ -0,0 +1,482 @@ +import { useEffect } from "react"; +import { useForm, Controller, useFieldArray } from "react-hook-form"; +import { useNavigate, useParams } from "react-router-dom"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Button, + Paper, + Typography, + Grid, + TextField, + Autocomplete, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + IconButton, + Alert, + Chip, +} from "@mui/material"; +import { + Save as SaveIcon, + ArrowBack as BackIcon, + Add as AddIcon, + Delete as DeleteIcon, + Check as CheckIcon, + LocalShipping as ReceiveIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { purchaseService } from "../services/purchaseService"; +import { supplierService } from "../services/supplierService"; +import { articleService, warehouseLocationService } from "../../warehouse/services/warehouseService"; +import { CreatePurchaseOrderDto, UpdatePurchaseOrderDto, PurchaseOrderStatus } from "../types"; +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import dayjs from "dayjs"; + +export default function PurchaseOrderFormPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { id } = useParams(); + const isEdit = Boolean(id); + const queryClient = useQueryClient(); + + const { control, handleSubmit, reset, watch, setValue, formState: { errors } } = useForm({ + defaultValues: { + orderDate: new Date().toISOString(), + expectedDeliveryDate: undefined, + supplierId: 0, + destinationWarehouseId: undefined, + notes: "", + lines: [], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "lines", + }); + + const { data: order, isLoading } = useQuery({ + queryKey: ["purchase-order", id], + queryFn: () => purchaseService.getById(Number(id)), + enabled: isEdit, + }); + + const { data: suppliers = [] } = useQuery({ + queryKey: ["suppliers"], + queryFn: () => supplierService.getAll(), + }); + + const { data: articles = [] } = useQuery({ + queryKey: ["articles"], + queryFn: () => articleService.getAll(), + }); + + const { data: warehouses = [] } = useQuery({ + queryKey: ["warehouses"], + queryFn: () => warehouseLocationService.getAll(), + }); + + useEffect(() => { + if (order) { + reset({ + orderDate: order.orderDate, + expectedDeliveryDate: order.expectedDeliveryDate, + supplierId: order.supplierId, + destinationWarehouseId: order.destinationWarehouseId, + notes: order.notes, + lines: order.lines.map(l => ({ + warehouseArticleId: l.warehouseArticleId, + description: l.description, + quantity: l.quantity, + unitPrice: l.unitPrice, + taxRate: l.taxRate, + discountPercent: l.discountPercent, + // Store existing ID for updates + id: l.id + })), + }); + } + }, [order, reset]); + + const createMutation = useMutation({ + mutationFn: (data: CreatePurchaseOrderDto) => purchaseService.create(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["purchase-orders"] }); + navigate("/purchases/orders"); + }, + }); + + const updateMutation = useMutation({ + mutationFn: (data: UpdatePurchaseOrderDto) => purchaseService.update(Number(id), data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["purchase-orders"] }); + queryClient.invalidateQueries({ queryKey: ["purchase-order", id] }); + navigate("/purchases/orders"); + }, + }); + + const confirmMutation = useMutation({ + mutationFn: (id: number) => purchaseService.confirm(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["purchase-orders"] }); + queryClient.invalidateQueries({ queryKey: ["purchase-order", id] }); + }, + }); + + const receiveMutation = useMutation({ + mutationFn: (id: number) => purchaseService.receive(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["purchase-orders"] }); + queryClient.invalidateQueries({ queryKey: ["purchase-order", id] }); + }, + }); + + const onSubmit = (data: CreatePurchaseOrderDto) => { + if (isEdit) { + updateMutation.mutate(data as UpdatePurchaseOrderDto); + } else { + createMutation.mutate(data); + } + }; + + const handleArticleChange = (index: number, articleId: number | null) => { + if (!articleId) return; + const article = articles.find(a => a.id === articleId); + if (article) { + setValue(`lines.${index}.warehouseArticleId`, article.id); + setValue(`lines.${index}.description`, article.description); + setValue(`lines.${index}.unitPrice`, article.lastPurchaseCost || 0); + setValue(`lines.${index}.taxRate`, 22); // Default tax rate + } + }; + + const calculateLineTotal = (index: number) => { + const lines = watch("lines"); + const line = lines[index]; + if (!line) return 0; + const netPrice = line.unitPrice * (1 - (line.discountPercent || 0) / 100); + return netPrice * line.quantity; + }; + + const calculateTotal = () => { + const lines = watch("lines"); + return lines.reduce((acc: number, _line: any, index: number) => acc + calculateLineTotal(index), 0); + }; + + const isReadOnly = isEdit && order?.status !== PurchaseOrderStatus.Draft; + + if (isEdit && isLoading) return Loading...; + + return ( + + + + + + + {isEdit ? `${t("purchases.orders.editOrder")} ${order?.orderNumber}` : t("purchases.orders.newOrder")} + + {isEdit && order && ( + + )} + + + + + {isEdit && order?.status === PurchaseOrderStatus.Draft && ( + + )} + + {isEdit && (order?.status === PurchaseOrderStatus.Confirmed || order?.status === PurchaseOrderStatus.PartiallyReceived) && ( + + )} + + {!isReadOnly && ( + + )} + + + + {(createMutation.isError || updateMutation.isError || confirmMutation.isError || receiveMutation.isError) && ( + + {t("common.error")} + + )} + + + + + ( + field.onChange(date?.toISOString())} + disabled={isReadOnly} + slotProps={{ textField: { fullWidth: true } }} + /> + )} + /> + + + ( + field.onChange(date?.toISOString())} + disabled={isReadOnly} + slotProps={{ textField: { fullWidth: true } }} + /> + )} + /> + + + ( + option.name} + value={suppliers.find(s => s.id === field.value) || null} + onChange={(_, newValue) => field.onChange(newValue?.id)} + disabled={isReadOnly} + renderInput={(params) => ( + + )} + /> + )} + /> + + + ( + option.name} + value={warehouses.find(w => w.id === field.value) || null} + onChange={(_, newValue) => field.onChange(newValue?.id)} + disabled={isReadOnly} + renderInput={(params) => ( + + )} + /> + )} + /> + + + ( + + )} + /> + + + + + + + {t("purchases.orders.fields.lineTotal")} + {!isReadOnly && ( + + )} + + + + + + + {t("purchases.orders.fields.article")} + {t("purchases.orders.fields.quantity")} + {t("purchases.orders.fields.unitPrice")} + {t("purchases.orders.fields.discount")} + {t("purchases.orders.fields.taxRate")} + {t("purchases.orders.fields.lineTotal")} + + + + + {fields.map((field: any, index: number) => ( + + + ( + `${option.code} - ${option.description}`} + value={articles.find(a => a.id === articleField.value) || null} + onChange={(_, newValue) => handleArticleChange(index, newValue?.id || null)} + disabled={isReadOnly} + renderInput={(params) => ( + + )} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + {new Intl.NumberFormat("it-IT", { style: "currency", currency: "EUR" }).format(calculateLineTotal(index))} + + + {!isReadOnly && ( + remove(index)}> + + + )} + + + ))} + + + {t("purchases.orders.totals.gross")} + + + + {new Intl.NumberFormat("it-IT", { style: "currency", currency: "EUR" }).format(calculateTotal())} + + + + + +
+
+
+
+ ); +} diff --git a/frontend/src/modules/purchases/pages/PurchaseOrdersPage.tsx b/frontend/src/modules/purchases/pages/PurchaseOrdersPage.tsx new file mode 100644 index 0000000..5e9bc59 --- /dev/null +++ b/frontend/src/modules/purchases/pages/PurchaseOrdersPage.tsx @@ -0,0 +1,174 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { + Box, + Typography, + Button, + Paper, + Chip, + IconButton, + Tooltip, +} from "@mui/material"; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridToolbar, +} from "@mui/x-data-grid"; +import { + Add as AddIcon, + Visibility as ViewIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { purchaseService } from "../services/purchaseService"; +import { PurchaseOrderDto, PurchaseOrderStatus } from "../types"; +import dayjs from "dayjs"; + +export default function PurchaseOrdersPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const { data: orders = [], isLoading } = useQuery({ + queryKey: ["purchase-orders"], + queryFn: () => purchaseService.getAll(), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => purchaseService.delete(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["purchase-orders"] }); + }, + }); + + const handleCreate = () => { + navigate("/purchases/orders/new"); + }; + + const handleView = (id: number) => { + navigate(`/purchases/orders/${id}`); + }; + + const handleDelete = (id: number) => { + if (confirm(t("common.confirmDelete"))) { + deleteMutation.mutate(id); + } + }; + + const getStatusChip = (status: PurchaseOrderStatus) => { + const label = t(`purchases.order.status.${PurchaseOrderStatus[status]}`); + switch (status) { + case PurchaseOrderStatus.Draft: + return ; + case PurchaseOrderStatus.Confirmed: + return ; + case PurchaseOrderStatus.PartiallyReceived: + return ; + case PurchaseOrderStatus.Received: + return ; + case PurchaseOrderStatus.Cancelled: + return ; + default: + return ; + } + }; + + const columns: GridColDef[] = [ + { field: "orderNumber", headerName: t("purchases.order.columns.number"), width: 150 }, + { + field: "orderDate", + headerName: t("purchases.order.columns.date"), + width: 120, + valueFormatter: (value) => dayjs(value).format("DD/MM/YYYY"), + }, + { field: "supplierName", headerName: t("purchases.order.columns.supplier"), flex: 1, minWidth: 200 }, + { + field: "status", + headerName: t("purchases.order.columns.status"), + width: 150, + renderCell: (params: GridRenderCellParams) => + getStatusChip(params.row.status), + }, + { + field: "totalGross", + headerName: t("purchases.order.columns.total"), + width: 120, + valueFormatter: (value) => + new Intl.NumberFormat("it-IT", { + style: "currency", + currency: "EUR", + }).format(value), + }, + { + field: "actions", + headerName: t("common.actions"), + width: 120, + sortable: false, + renderCell: (params: GridRenderCellParams) => ( + + + handleView(params.row.id)}> + + + + {(params.row.status === PurchaseOrderStatus.Draft || + params.row.status === PurchaseOrderStatus.Cancelled) && ( + + handleDelete(params.row.id)} + > + + + + )} + + ), + }, + ]; + + return ( + + + {t("purchases.order.title")} + + + + + + + + ); +} diff --git a/frontend/src/modules/purchases/pages/SupplierFormPage.tsx b/frontend/src/modules/purchases/pages/SupplierFormPage.tsx new file mode 100644 index 0000000..ecd8045 --- /dev/null +++ b/frontend/src/modules/purchases/pages/SupplierFormPage.tsx @@ -0,0 +1,315 @@ +import { useEffect } from "react"; +import { useForm, Controller } from "react-hook-form"; +import { useNavigate, useParams } from "react-router-dom"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Button, + Paper, + Typography, + Grid, + TextField, + FormControlLabel, + Switch, + Alert, +} from "@mui/material"; +import { Save as SaveIcon, ArrowBack as BackIcon } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { supplierService } from "../services/supplierService"; +import { CreateSupplierDto, UpdateSupplierDto } from "../types"; + +export default function SupplierFormPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { id } = useParams(); + const isEdit = Boolean(id); + const queryClient = useQueryClient(); + + const { control, handleSubmit, reset, formState: { errors } } = useForm({ + defaultValues: { + name: "", + vatNumber: "", + fiscalCode: "", + address: "", + city: "", + province: "", + zipCode: "", + country: "Italia", + email: "", + pec: "", + phone: "", + website: "", + paymentTerms: "", + notes: "", + isActive: true, + }, + }); + + const { data: supplier, isLoading } = useQuery({ + queryKey: ["supplier", id], + queryFn: () => supplierService.getById(Number(id)), + enabled: isEdit, + }); + + useEffect(() => { + if (supplier) { + reset(supplier); + } + }, [supplier, reset]); + + const createMutation = useMutation({ + mutationFn: (data: CreateSupplierDto) => supplierService.create(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["suppliers"] }); + navigate("/purchases/suppliers"); + }, + }); + + const updateMutation = useMutation({ + mutationFn: (data: UpdateSupplierDto) => supplierService.update(Number(id), data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["suppliers"] }); + queryClient.invalidateQueries({ queryKey: ["supplier", id] }); + navigate("/purchases/suppliers"); + }, + }); + + const onSubmit = (data: CreateSupplierDto & { isActive: boolean }) => { + if (isEdit) { + updateMutation.mutate(data); + } else { + createMutation.mutate(data); + } + }; + + if (isEdit && isLoading) return Loading...; + + return ( + + + + + {isEdit ? t("purchases.supplier.editTitle") : t("purchases.supplier.createTitle")} + + + + {(createMutation.isError || updateMutation.isError) && ( + + {t("common.error")} + + )} + + +
+ + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + } + label={t("common.active")} + /> + )} + /> + + + + + + +
+
+
+ ); +} diff --git a/frontend/src/modules/purchases/pages/SuppliersPage.tsx b/frontend/src/modules/purchases/pages/SuppliersPage.tsx new file mode 100644 index 0000000..0a587da --- /dev/null +++ b/frontend/src/modules/purchases/pages/SuppliersPage.tsx @@ -0,0 +1,145 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { + Box, + Typography, + Button, + Paper, + Chip, + IconButton, + Tooltip, +} from "@mui/material"; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridToolbar, +} from "@mui/x-data-grid"; +import { + Add as AddIcon, + Edit as EditIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { supplierService } from "../services/supplierService"; +import { SupplierDto } from "../types"; + +export default function SuppliersPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const { data: suppliers = [], isLoading } = useQuery({ + queryKey: ["suppliers"], + queryFn: () => supplierService.getAll(), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => supplierService.delete(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["suppliers"] }); + }, + }); + + const handleCreate = () => { + navigate("/purchases/suppliers/new"); + }; + + const handleEdit = (id: number) => { + navigate(`/purchases/suppliers/${id}`); + }; + + const handleDelete = (id: number) => { + if (confirm(t("common.confirmDelete"))) { + deleteMutation.mutate(id); + } + }; + + const columns: GridColDef[] = [ + { field: "code", headerName: t("purchases.suppliers.columns.code"), width: 120 }, + { field: "name", headerName: t("purchases.suppliers.columns.name"), flex: 1, minWidth: 200 }, + { field: "vatNumber", headerName: t("purchases.suppliers.columns.vatNumber"), width: 150 }, + { field: "email", headerName: t("purchases.suppliers.columns.email"), width: 200 }, + { field: "phone", headerName: t("purchases.suppliers.columns.phone"), width: 150 }, + { field: "city", headerName: t("purchases.suppliers.columns.city"), width: 150 }, + { + field: "isActive", + headerName: t("purchases.suppliers.columns.status"), + width: 120, + renderCell: (params: GridRenderCellParams) => ( + + ), + }, + { + field: "actions", + headerName: t("common.actions"), + width: 120, + sortable: false, + renderCell: (params: GridRenderCellParams) => ( + + + handleEdit(params.row.id)}> + + + + + handleDelete(params.row.id)} + > + + + + + ), + }, + ]; + + return ( + + + {t("purchases.suppliers.title")} + + + + + + + + ); +} diff --git a/frontend/src/modules/purchases/routes.tsx b/frontend/src/modules/purchases/routes.tsx new file mode 100644 index 0000000..3044097 --- /dev/null +++ b/frontend/src/modules/purchases/routes.tsx @@ -0,0 +1,18 @@ +import { Routes, Route } from "react-router-dom"; +import SuppliersPage from "./pages/SuppliersPage"; +import SupplierFormPage from "./pages/SupplierFormPage"; +import PurchaseOrdersPage from "./pages/PurchaseOrdersPage"; +import PurchaseOrderFormPage from "./pages/PurchaseOrderFormPage"; + +export default function PurchasesRoutes() { + return ( + + } /> + } /> + } /> + } /> + } /> + } /> + + ); +} diff --git a/frontend/src/modules/purchases/services/purchaseService.ts b/frontend/src/modules/purchases/services/purchaseService.ts new file mode 100644 index 0000000..5cf4caf --- /dev/null +++ b/frontend/src/modules/purchases/services/purchaseService.ts @@ -0,0 +1,40 @@ +import axios from 'axios'; +import { PurchaseOrderDto, CreatePurchaseOrderDto, UpdatePurchaseOrderDto } from '../types'; + +const API_URL = '/api/purchases/orders'; + +export const purchaseService = { + getAll: async (): Promise => { + const response = await axios.get(API_URL); + return response.data; + }, + + getById: async (id: number): Promise => { + const response = await axios.get(`${API_URL}/${id}`); + return response.data; + }, + + create: async (order: CreatePurchaseOrderDto): Promise => { + const response = await axios.post(API_URL, order); + return response.data; + }, + + update: async (id: number, order: UpdatePurchaseOrderDto): Promise => { + const response = await axios.put(`${API_URL}/${id}`, order); + return response.data; + }, + + delete: async (id: number): Promise => { + await axios.delete(`${API_URL}/${id}`); + }, + + confirm: async (id: number): Promise => { + const response = await axios.post(`${API_URL}/${id}/confirm`); + return response.data; + }, + + receive: async (id: number): Promise => { + const response = await axios.post(`${API_URL}/${id}/receive`); + return response.data; + } +}; diff --git a/frontend/src/modules/purchases/services/supplierService.ts b/frontend/src/modules/purchases/services/supplierService.ts new file mode 100644 index 0000000..fc8f9a1 --- /dev/null +++ b/frontend/src/modules/purchases/services/supplierService.ts @@ -0,0 +1,30 @@ +import axios from 'axios'; +import { SupplierDto, CreateSupplierDto, UpdateSupplierDto } from '../types'; + +const API_URL = '/api/purchases/suppliers'; + +export const supplierService = { + getAll: async (): Promise => { + const response = await axios.get(API_URL); + return response.data; + }, + + getById: async (id: number): Promise => { + const response = await axios.get(`${API_URL}/${id}`); + return response.data; + }, + + create: async (supplier: CreateSupplierDto): Promise => { + const response = await axios.post(API_URL, supplier); + return response.data; + }, + + update: async (id: number, supplier: UpdateSupplierDto): Promise => { + const response = await axios.put(`${API_URL}/${id}`, supplier); + return response.data; + }, + + delete: async (id: number): Promise => { + await axios.delete(`${API_URL}/${id}`); + } +}; diff --git a/frontend/src/modules/purchases/types/index.ts b/frontend/src/modules/purchases/types/index.ts new file mode 100644 index 0000000..070bcc2 --- /dev/null +++ b/frontend/src/modules/purchases/types/index.ts @@ -0,0 +1,121 @@ +export interface SupplierDto { + id: number; + code: string; + name: string; + vatNumber?: string; + fiscalCode?: string; + address?: string; + city?: string; + province?: string; + zipCode?: string; + country?: string; + email?: string; + pec?: string; + phone?: string; + website?: string; + paymentTerms?: string; + notes?: string; + isActive: boolean; + createdAt: string; + updatedAt?: string; +} + +export interface CreateSupplierDto { + name: string; + vatNumber?: string; + fiscalCode?: string; + address?: string; + city?: string; + province?: string; + zipCode?: string; + country?: string; + email?: string; + pec?: string; + phone?: string; + website?: string; + paymentTerms?: string; + notes?: string; +} + +export interface UpdateSupplierDto extends Partial { + isActive?: boolean; +} + +export enum PurchaseOrderStatus { + Draft = 0, + Confirmed = 1, + PartiallyReceived = 2, + Received = 3, + Cancelled = 4 +} + +export interface PurchaseOrderDto { + id: number; + orderNumber: string; + orderDate: string; + expectedDeliveryDate?: string; + supplierId: number; + supplierName: string; + status: PurchaseOrderStatus; + destinationWarehouseId?: number; + destinationWarehouseName?: string; + notes?: string; + totalNet: number; + totalTax: number; + totalGross: number; + createdAt: string; + updatedAt?: string; + lines: PurchaseOrderLineDto[]; +} + +export interface PurchaseOrderLineDto { + id: number; + purchaseOrderId: number; + warehouseArticleId: number; + articleCode: string; + articleDescription: string; + description: string; + quantity: number; + receivedQuantity: number; + unitPrice: number; + taxRate: number; + discountPercent: number; + lineTotal: number; +} + +export interface CreatePurchaseOrderDto { + orderDate: string; + expectedDeliveryDate?: string; + supplierId: number; + destinationWarehouseId?: number; + notes?: string; + lines: CreatePurchaseOrderLineDto[]; +} + +export interface CreatePurchaseOrderLineDto { + warehouseArticleId: number; + description?: string; + quantity: number; + unitPrice: number; + taxRate: number; + discountPercent: number; +} + +export interface UpdatePurchaseOrderDto { + orderDate: string; + expectedDeliveryDate?: string; + destinationWarehouseId?: number; + notes?: string; + lines: UpdatePurchaseOrderLineDto[]; +} + +export interface UpdatePurchaseOrderLineDto { + id?: number; + warehouseArticleId: number; + description?: string; + quantity: number; + unitPrice: number; + taxRate: number; + discountPercent: number; + isDeleted?: boolean; +} diff --git a/frontend/src/modules/sales/pages/SalesOrderFormPage.tsx b/frontend/src/modules/sales/pages/SalesOrderFormPage.tsx new file mode 100644 index 0000000..fa53b4d --- /dev/null +++ b/frontend/src/modules/sales/pages/SalesOrderFormPage.tsx @@ -0,0 +1,455 @@ +import { useEffect } from "react"; +import { useForm, Controller, useFieldArray } from "react-hook-form"; +import { useNavigate, useParams } from "react-router-dom"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Button, + Paper, + Typography, + Grid, + TextField, + Autocomplete, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + IconButton, + Alert, + Chip, +} from "@mui/material"; +import { + Save as SaveIcon, + ArrowBack as BackIcon, + Add as AddIcon, + Delete as DeleteIcon, + Check as CheckIcon, + LocalShipping as ShipIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { salesService } from "../services/salesService"; +import { clientiService } from "../../../services/lookupService"; +import { articleService } from "../../warehouse/services/warehouseService"; +import { CreateSalesOrderDto, UpdateSalesOrderDto, SalesOrderStatus } from "../types"; +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import dayjs from "dayjs"; + +export default function SalesOrderFormPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { id } = useParams(); + const isEdit = Boolean(id); + const queryClient = useQueryClient(); + + const { control, handleSubmit, reset, watch, setValue, formState: { errors } } = useForm({ + defaultValues: { + orderDate: new Date().toISOString(), + expectedDeliveryDate: undefined, + customerId: 0, + notes: "", + lines: [], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "lines", + }); + + const { data: order, isLoading } = useQuery({ + queryKey: ["sales-order", id], + queryFn: () => salesService.getById(Number(id)), + enabled: isEdit, + }); + + const { data: customers = [] } = useQuery({ + queryKey: ["customers"], + queryFn: () => clientiService.getAll(), + }); + + const { data: articles = [] } = useQuery({ + queryKey: ["articles"], + queryFn: () => articleService.getAll(), + }); + + useEffect(() => { + if (order) { + reset({ + orderDate: order.orderDate, + expectedDeliveryDate: order.expectedDeliveryDate, + customerId: order.customerId, + notes: order.notes, + lines: order.lines.map(l => ({ + warehouseArticleId: l.warehouseArticleId, + description: l.description, + quantity: l.quantity, + unitPrice: l.unitPrice, + taxRate: l.taxRate, + discountPercent: l.discountPercent, + // Store existing ID for updates + id: l.id + })), + }); + } + }, [order, reset]); + + const createMutation = useMutation({ + mutationFn: (data: CreateSalesOrderDto) => salesService.create(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["sales-orders"] }); + navigate("/sales/orders"); + }, + }); + + const updateMutation = useMutation({ + mutationFn: (data: UpdateSalesOrderDto) => salesService.update(Number(id), data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["sales-orders"] }); + queryClient.invalidateQueries({ queryKey: ["sales-order", id] }); + navigate("/sales/orders"); + }, + }); + + const confirmMutation = useMutation({ + mutationFn: (id: number) => salesService.confirm(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["sales-orders"] }); + queryClient.invalidateQueries({ queryKey: ["sales-order", id] }); + }, + }); + + const shipMutation = useMutation({ + mutationFn: (id: number) => salesService.ship(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["sales-orders"] }); + queryClient.invalidateQueries({ queryKey: ["sales-order", id] }); + }, + }); + + const onSubmit = (data: CreateSalesOrderDto) => { + if (isEdit) { + updateMutation.mutate(data as UpdateSalesOrderDto); + } else { + createMutation.mutate(data); + } + }; + + const handleArticleChange = (index: number, articleId: number | null) => { + if (!articleId) return; + const article = articles.find(a => a.id === articleId); + if (article) { + setValue(`lines.${index}.warehouseArticleId`, article.id); + setValue(`lines.${index}.description`, article.description); + // Use baseSellingPrice if available, otherwise 0 or cost + setValue(`lines.${index}.unitPrice`, article.baseSellingPrice || 0); + setValue(`lines.${index}.taxRate`, 22); // Default tax rate + } + }; + + const calculateLineTotal = (index: number) => { + const lines = watch("lines"); + const line = lines[index]; + if (!line) return 0; + const netPrice = line.unitPrice * (1 - (line.discountPercent || 0) / 100); + return netPrice * line.quantity; + }; + + const calculateTotal = () => { + const lines = watch("lines"); + return lines.reduce((acc: number, _line: any, index: number) => acc + calculateLineTotal(index), 0); + }; + + const isReadOnly = isEdit && order?.status !== SalesOrderStatus.Draft; + + if (isEdit && isLoading) return Loading...; + + return ( + + + + + + + {isEdit ? `${t("sales.order.editTitle")} ${order?.orderNumber}` : t("sales.order.createTitle")} + + {isEdit && order && ( + + )} + + + + + {isEdit && order?.status === SalesOrderStatus.Draft && ( + + )} + + {isEdit && (order?.status === SalesOrderStatus.Confirmed || order?.status === SalesOrderStatus.PartiallyShipped) && ( + + )} + + {!isReadOnly && ( + + )} + + + + {(createMutation.isError || updateMutation.isError || confirmMutation.isError || shipMutation.isError) && ( + + {t("common.error")} + + )} + + + + + ( + field.onChange(date?.toISOString())} + disabled={isReadOnly} + slotProps={{ textField: { fullWidth: true } }} + /> + )} + /> + + + ( + field.onChange(date?.toISOString())} + disabled={isReadOnly} + slotProps={{ textField: { fullWidth: true } }} + /> + )} + /> + + + ( + option.ragioneSociale} + value={customers.find(c => c.id === field.value) || null} + onChange={(_, newValue) => field.onChange(newValue?.id)} + disabled={isReadOnly} + renderInput={(params) => ( + + )} + /> + )} + /> + + + ( + + )} + /> + + + + + + + {t("sales.order.fields.lineTotal")} + {!isReadOnly && ( + + )} + + + + + + + {t("sales.order.fields.article")} + {t("sales.order.fields.quantity")} + {t("sales.order.fields.unitPrice")} + {t("sales.order.fields.discount")} + {t("sales.order.fields.taxRate")} + {t("sales.order.fields.lineTotal")} + + + + + {fields.map((field: any, index: number) => ( + + + ( + `${option.code} - ${option.description}`} + value={articles.find(a => a.id === articleField.value) || null} + onChange={(_, newValue) => handleArticleChange(index, newValue?.id || null)} + disabled={isReadOnly} + renderInput={(params) => ( + + )} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + ( + field.onChange(Number(e.target.value))} + /> + )} + /> + + + {new Intl.NumberFormat("it-IT", { style: "currency", currency: "EUR" }).format(calculateLineTotal(index))} + + + {!isReadOnly && ( + remove(index)}> + + + )} + + + ))} + + + {t("sales.order.totals.gross")} + + + + {new Intl.NumberFormat("it-IT", { style: "currency", currency: "EUR" }).format(calculateTotal())} + + + + + +
+
+
+
+ ); +} diff --git a/frontend/src/modules/sales/pages/SalesOrdersPage.tsx b/frontend/src/modules/sales/pages/SalesOrdersPage.tsx new file mode 100644 index 0000000..a4d7eb9 --- /dev/null +++ b/frontend/src/modules/sales/pages/SalesOrdersPage.tsx @@ -0,0 +1,176 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { + Box, + Typography, + Button, + Paper, + Chip, + IconButton, + Tooltip, +} from "@mui/material"; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridToolbar, +} from "@mui/x-data-grid"; +import { + Add as AddIcon, + Visibility as ViewIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { salesService } from "../services/salesService"; +import { SalesOrderDto, SalesOrderStatus } from "../types"; +import dayjs from "dayjs"; + +export default function SalesOrdersPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const { data: orders = [], isLoading } = useQuery({ + queryKey: ["sales-orders"], + queryFn: () => salesService.getAll(), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => salesService.delete(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["sales-orders"] }); + }, + }); + + const handleCreate = () => { + navigate("/sales/orders/new"); + }; + + const handleView = (id: number) => { + navigate(`/sales/orders/${id}`); + }; + + const handleDelete = (id: number) => { + if (confirm(t("common.confirmDelete"))) { + deleteMutation.mutate(id); + } + }; + + const getStatusChip = (status: SalesOrderStatus) => { + const label = t(`sales.order.status.${SalesOrderStatus[status]}`); + switch (status) { + case SalesOrderStatus.Draft: + return ; + case SalesOrderStatus.Confirmed: + return ; + case SalesOrderStatus.PartiallyShipped: + return ; + case SalesOrderStatus.Shipped: + return ; + case SalesOrderStatus.Invoiced: + return ; + case SalesOrderStatus.Cancelled: + return ; + default: + return ; + } + }; + + const columns: GridColDef[] = [ + { field: "orderNumber", headerName: t("sales.order.columns.number"), width: 150 }, + { + field: "orderDate", + headerName: t("sales.order.columns.date"), + width: 120, + valueFormatter: (value) => dayjs(value).format("DD/MM/YYYY"), + }, + { field: "customerName", headerName: t("sales.order.columns.customer"), flex: 1, minWidth: 200 }, + { + field: "status", + headerName: t("sales.order.columns.status"), + width: 150, + renderCell: (params: GridRenderCellParams) => + getStatusChip(params.row.status), + }, + { + field: "totalGross", + headerName: t("sales.order.columns.total"), + width: 120, + valueFormatter: (value) => + new Intl.NumberFormat("it-IT", { + style: "currency", + currency: "EUR", + }).format(value), + }, + { + field: "actions", + headerName: t("common.actions"), + width: 120, + sortable: false, + renderCell: (params: GridRenderCellParams) => ( + + + handleView(params.row.id)}> + + + + {(params.row.status === SalesOrderStatus.Draft || + params.row.status === SalesOrderStatus.Cancelled) && ( + + handleDelete(params.row.id)} + > + + + + )} + + ), + }, + ]; + + return ( + + + {t("sales.order.title")} + + + + + + + + ); +} diff --git a/frontend/src/modules/sales/routes.tsx b/frontend/src/modules/sales/routes.tsx new file mode 100644 index 0000000..26244e1 --- /dev/null +++ b/frontend/src/modules/sales/routes.tsx @@ -0,0 +1,13 @@ +import { Routes, Route } from "react-router-dom"; +import SalesOrdersPage from "./pages/SalesOrdersPage"; +import SalesOrderFormPage from "./pages/SalesOrderFormPage"; + +export default function SalesRoutes() { + return ( + + } /> + } /> + } /> + + ); +} diff --git a/frontend/src/modules/sales/services/salesService.ts b/frontend/src/modules/sales/services/salesService.ts new file mode 100644 index 0000000..05cdcfa --- /dev/null +++ b/frontend/src/modules/sales/services/salesService.ts @@ -0,0 +1,40 @@ +import axios from 'axios'; +import { SalesOrderDto, CreateSalesOrderDto, UpdateSalesOrderDto } from '../types'; + +const API_URL = '/api/sales/orders'; + +export const salesService = { + getAll: async (): Promise => { + const response = await axios.get(API_URL); + return response.data; + }, + + getById: async (id: number): Promise => { + const response = await axios.get(`${API_URL}/${id}`); + return response.data; + }, + + create: async (order: CreateSalesOrderDto): Promise => { + const response = await axios.post(API_URL, order); + return response.data; + }, + + update: async (id: number, order: UpdateSalesOrderDto): Promise => { + const response = await axios.put(`${API_URL}/${id}`, order); + return response.data; + }, + + delete: async (id: number): Promise => { + await axios.delete(`${API_URL}/${id}`); + }, + + confirm: async (id: number): Promise => { + const response = await axios.post(`${API_URL}/${id}/confirm`); + return response.data; + }, + + ship: async (id: number): Promise => { + const response = await axios.post(`${API_URL}/${id}/ship`); + return response.data; + } +}; diff --git a/frontend/src/modules/sales/types/index.ts b/frontend/src/modules/sales/types/index.ts new file mode 100644 index 0000000..70dd6e5 --- /dev/null +++ b/frontend/src/modules/sales/types/index.ts @@ -0,0 +1,77 @@ + + +export enum SalesOrderStatus { + Draft = 0, + Confirmed = 1, + PartiallyShipped = 2, + Shipped = 3, + Invoiced = 4, + Cancelled = 5 +} + +export interface SalesOrderDto { + id: number; + orderNumber: string; + orderDate: string; + expectedDeliveryDate?: string; + customerId: number; + customerName: string; + status: SalesOrderStatus; + notes?: string; + totalNet: number; + totalTax: number; + totalGross: number; + createdAt: string; + updatedAt?: string; + lines: SalesOrderLineDto[]; +} + +export interface SalesOrderLineDto { + id: number; + salesOrderId: number; + warehouseArticleId: number; + articleCode: string; + articleDescription: string; + description: string; + quantity: number; + shippedQuantity: number; + unitPrice: number; + taxRate: number; + discountPercent: number; + lineTotal: number; +} + +export interface CreateSalesOrderDto { + orderDate: string; + expectedDeliveryDate?: string; + customerId: number; + notes?: string; + lines: CreateSalesOrderLineDto[]; +} + +export interface CreateSalesOrderLineDto { + warehouseArticleId: number; + description?: string; + quantity: number; + unitPrice: number; + taxRate: number; + discountPercent: number; +} + +export interface UpdateSalesOrderDto { + orderDate: string; + expectedDeliveryDate?: string; + notes?: string; + lines: UpdateSalesOrderLineDto[]; +} + +export interface UpdateSalesOrderLineDto { + id?: number; + warehouseArticleId: number; + description?: string; + quantity: number; + unitPrice: number; + taxRate: number; + discountPercent: number; + isDeleted?: boolean; +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 8b0f57b..74b1f1d 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -4,4 +4,18 @@ import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + server: { + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + secure: false, + }, + '/hubs': { + target: 'http://localhost:5000', + changeOrigin: true, + ws: true, + } + } + } }) diff --git a/src/Apollinare.API/Modules/Production/Controllers/BillOfMaterialsController.cs b/src/Apollinare.API/Modules/Production/Controllers/BillOfMaterialsController.cs new file mode 100644 index 0000000..2bfa622 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Controllers/BillOfMaterialsController.cs @@ -0,0 +1,59 @@ +using Apollinare.API.Modules.Production.Dtos; +using Apollinare.API.Modules.Production.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Modules.Production.Controllers; + +[ApiController] +[Route("api/production/bom")] +public class BillOfMaterialsController : ControllerBase +{ + private readonly IProductionService _productionService; + + public BillOfMaterialsController(IProductionService productionService) + { + _productionService = productionService; + } + + [HttpGet] + public async Task>> GetAll() + { + return Ok(await _productionService.GetBillOfMaterialsAsync()); + } + + [HttpGet("{id}")] + public async Task> GetById(int id) + { + var bom = await _productionService.GetBillOfMaterialsByIdAsync(id); + if (bom == null) return NotFound(); + return Ok(bom); + } + + [HttpPost] + public async Task> Create(CreateBillOfMaterialsDto dto) + { + var bom = await _productionService.CreateBillOfMaterialsAsync(dto); + return CreatedAtAction(nameof(GetById), new { id = bom.Id }, bom); + } + + [HttpPut("{id}")] + public async Task> Update(int id, UpdateBillOfMaterialsDto dto) + { + try + { + var bom = await _productionService.UpdateBillOfMaterialsAsync(id, dto); + return Ok(bom); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + } + + [HttpDelete("{id}")] + public async Task Delete(int id) + { + await _productionService.DeleteBillOfMaterialsAsync(id); + return NoContent(); + } +} diff --git a/src/Apollinare.API/Modules/Production/Controllers/MrpController.cs b/src/Apollinare.API/Modules/Production/Controllers/MrpController.cs new file mode 100644 index 0000000..c9dec35 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Controllers/MrpController.cs @@ -0,0 +1,38 @@ +using Apollinare.API.Modules.Production.Dtos; +using Apollinare.API.Modules.Production.Services; +using Apollinare.Domain.Entities.Production; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Modules.Production.Controllers; + +[ApiController] +[Route("api/production/mrp")] +public class MrpController : ControllerBase +{ + private readonly IMrpService _mrpService; + + public MrpController(IMrpService mrpService) + { + _mrpService = mrpService; + } + + [HttpPost("run")] + public async Task RunMrp([FromBody] MrpConfigurationDto config) + { + await _mrpService.RunMrpAsync(config); + return Ok(new { message = "MRP Run completed successfully" }); + } + + [HttpGet("suggestions")] + public async Task>> GetSuggestions([FromQuery] bool includeProcessed = false) + { + return await _mrpService.GetSuggestionsAsync(includeProcessed); + } + + [HttpPost("suggestions/{id}/process")] + public async Task ProcessSuggestion(int id) + { + await _mrpService.ProcessSuggestionAsync(id); + return Ok(); + } +} diff --git a/src/Apollinare.API/Modules/Production/Controllers/ProductionCyclesController.cs b/src/Apollinare.API/Modules/Production/Controllers/ProductionCyclesController.cs new file mode 100644 index 0000000..975d045 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Controllers/ProductionCyclesController.cs @@ -0,0 +1,65 @@ +using Apollinare.API.Modules.Production.Dtos; +using Apollinare.API.Modules.Production.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Modules.Production.Controllers; + +[ApiController] +[Route("api/production/cycles")] +public class ProductionCyclesController : ControllerBase +{ + private readonly IProductionService _productionService; + + public ProductionCyclesController(IProductionService productionService) + { + _productionService = productionService; + } + + [HttpGet] + public async Task>> GetProductionCycles() + { + return await _productionService.GetProductionCyclesAsync(); + } + + [HttpGet("{id}")] + public async Task> GetProductionCycle(int id) + { + var cycle = await _productionService.GetProductionCycleByIdAsync(id); + if (cycle == null) return NotFound(); + return cycle; + } + + [HttpPost] + public async Task> CreateProductionCycle(CreateProductionCycleDto dto) + { + try + { + var cycle = await _productionService.CreateProductionCycleAsync(dto); + return CreatedAtAction(nameof(GetProductionCycle), new { id = cycle.Id }, cycle); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { message = ex.Message }); + } + } + + [HttpPut("{id}")] + public async Task> UpdateProductionCycle(int id, UpdateProductionCycleDto dto) + { + try + { + return await _productionService.UpdateProductionCycleAsync(id, dto); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + } + + [HttpDelete("{id}")] + public async Task DeleteProductionCycle(int id) + { + await _productionService.DeleteProductionCycleAsync(id); + return NoContent(); + } +} diff --git a/src/Apollinare.API/Modules/Production/Controllers/ProductionOrdersController.cs b/src/Apollinare.API/Modules/Production/Controllers/ProductionOrdersController.cs new file mode 100644 index 0000000..489e969 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Controllers/ProductionOrdersController.cs @@ -0,0 +1,105 @@ +using Apollinare.API.Modules.Production.Dtos; +using Apollinare.API.Modules.Production.Services; +using Apollinare.Domain.Entities.Production; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Modules.Production.Controllers; + +[ApiController] +[Route("api/production/orders")] +public class ProductionOrdersController : ControllerBase +{ + private readonly IProductionService _productionService; + + public ProductionOrdersController(IProductionService productionService) + { + _productionService = productionService; + } + + [HttpGet] + public async Task>> GetAll() + { + return Ok(await _productionService.GetProductionOrdersAsync()); + } + + [HttpGet("{id}")] + public async Task> GetById(int id) + { + var order = await _productionService.GetProductionOrderByIdAsync(id); + if (order == null) return NotFound(); + return Ok(order); + } + + [HttpPost] + public async Task> Create(CreateProductionOrderDto dto) + { + var order = await _productionService.CreateProductionOrderAsync(dto); + return CreatedAtAction(nameof(GetById), new { id = order.Id }, order); + } + + [HttpPut("{id}")] + public async Task> Update(int id, UpdateProductionOrderDto dto) + { + try + { + var order = await _productionService.UpdateProductionOrderAsync(id, dto); + return Ok(order); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPut("{id}/status")] + public async Task> ChangeStatus(int id, [FromBody] ProductionOrderStatus status) + { + try + { + return await _productionService.ChangeProductionOrderStatusAsync(id, status); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { message = ex.Message }); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + } + + [HttpPut("{id}/phases/{phaseId}")] + public async Task> UpdatePhase(int id, int phaseId, UpdateProductionOrderPhaseDto dto) + { + try + { + return await _productionService.UpdateProductionOrderPhaseAsync(id, phaseId, dto); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("{id}")] + public async Task Delete(int id) + { + try + { + await _productionService.DeleteProductionOrderAsync(id); + return NoContent(); + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } +} diff --git a/src/Apollinare.API/Modules/Production/Controllers/WorkCentersController.cs b/src/Apollinare.API/Modules/Production/Controllers/WorkCentersController.cs new file mode 100644 index 0000000..b5e3e80 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Controllers/WorkCentersController.cs @@ -0,0 +1,72 @@ +using Apollinare.API.Modules.Production.Dtos; +using Apollinare.API.Modules.Production.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Modules.Production.Controllers; + +[ApiController] +[Route("api/production/work-centers")] +public class WorkCentersController : ControllerBase +{ + private readonly IProductionService _productionService; + + public WorkCentersController(IProductionService productionService) + { + _productionService = productionService; + } + + [HttpGet] + public async Task>> GetWorkCenters() + { + return await _productionService.GetWorkCentersAsync(); + } + + [HttpGet("{id}")] + public async Task> GetWorkCenter(int id) + { + var wc = await _productionService.GetWorkCenterByIdAsync(id); + if (wc == null) return NotFound(); + return wc; + } + + [HttpPost] + public async Task> CreateWorkCenter(CreateWorkCenterDto dto) + { + try + { + var wc = await _productionService.CreateWorkCenterAsync(dto); + return CreatedAtAction(nameof(GetWorkCenter), new { id = wc.Id }, wc); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { message = ex.Message }); + } + } + + [HttpPut("{id}")] + public async Task> UpdateWorkCenter(int id, UpdateWorkCenterDto dto) + { + try + { + return await _productionService.UpdateWorkCenterAsync(id, dto); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + } + + [HttpDelete("{id}")] + public async Task DeleteWorkCenter(int id) + { + try + { + await _productionService.DeleteWorkCenterAsync(id); + return NoContent(); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { message = ex.Message }); + } + } +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/BillOfMaterialsDto.cs b/src/Apollinare.API/Modules/Production/Dtos/BillOfMaterialsDto.cs new file mode 100644 index 0000000..c44cf27 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/BillOfMaterialsDto.cs @@ -0,0 +1,22 @@ +namespace Apollinare.API.Modules.Production.Dtos; + +public class BillOfMaterialsDto +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public int ArticleId { get; set; } + public string ArticleName { get; set; } = string.Empty; + public decimal Quantity { get; set; } + public bool IsActive { get; set; } + public List Components { get; set; } = new(); +} + +public class BillOfMaterialsComponentDto +{ + public int Id { get; set; } + public int ComponentArticleId { get; set; } + public string ComponentArticleName { get; set; } = string.Empty; + public decimal Quantity { get; set; } + public decimal ScrapPercentage { get; set; } +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/CreateBillOfMaterialsDto.cs b/src/Apollinare.API/Modules/Production/Dtos/CreateBillOfMaterialsDto.cs new file mode 100644 index 0000000..585b923 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/CreateBillOfMaterialsDto.cs @@ -0,0 +1,17 @@ +namespace Apollinare.API.Modules.Production.Dtos; + +public class CreateBillOfMaterialsDto +{ + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public int ArticleId { get; set; } + public decimal Quantity { get; set; } + public List Components { get; set; } = new(); +} + +public class CreateBillOfMaterialsComponentDto +{ + public int ComponentArticleId { get; set; } + public decimal Quantity { get; set; } + public decimal ScrapPercentage { get; set; } +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/CreateProductionOrderDto.cs b/src/Apollinare.API/Modules/Production/Dtos/CreateProductionOrderDto.cs new file mode 100644 index 0000000..ad55ccd --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/CreateProductionOrderDto.cs @@ -0,0 +1,13 @@ +namespace Apollinare.API.Modules.Production.Dtos; + +public class CreateProductionOrderDto +{ + public int ArticleId { get; set; } + public decimal Quantity { get; set; } + public DateTime StartDate { get; set; } + public DateTime DueDate { get; set; } + public string? Notes { get; set; } + public int? BillOfMaterialsId { get; set; } // Optional: create from BOM + public bool CreateChildOrders { get; set; } = false; // Optional: recursively create orders for sub-assemblies + public int? ParentProductionOrderId { get; set; } // Internal use for recursion +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/MrpConfigurationDto.cs b/src/Apollinare.API/Modules/Production/Dtos/MrpConfigurationDto.cs new file mode 100644 index 0000000..2ebdfb8 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/MrpConfigurationDto.cs @@ -0,0 +1,11 @@ +namespace Apollinare.API.Modules.Production.Dtos; + +public class MrpConfigurationDto +{ + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public bool IncludeSafetyStock { get; set; } = true; + public bool IncludeSalesOrders { get; set; } = true; + public bool IncludeForecasts { get; set; } = false; + public List? WarehouseIds { get; set; } +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/ProductionCycleDto.cs b/src/Apollinare.API/Modules/Production/Dtos/ProductionCycleDto.cs new file mode 100644 index 0000000..c567360 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/ProductionCycleDto.cs @@ -0,0 +1,65 @@ +namespace Apollinare.API.Modules.Production.Dtos; + +public class ProductionCycleDto +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public int ArticleId { get; set; } + public string ArticleName { get; set; } = string.Empty; + public bool IsDefault { get; set; } + public bool IsActive { get; set; } + public List Phases { get; set; } = new(); +} + +public class ProductionCyclePhaseDto +{ + public int Id { get; set; } + public int Sequence { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public int WorkCenterId { get; set; } + public string WorkCenterName { get; set; } = string.Empty; + public int DurationPerUnitMinutes { get; set; } + public int SetupTimeMinutes { get; set; } +} + +public class CreateProductionCycleDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public int ArticleId { get; set; } + public bool IsDefault { get; set; } + public List Phases { get; set; } = new(); +} + +public class CreateProductionCyclePhaseDto +{ + public int Sequence { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public int WorkCenterId { get; set; } + public int DurationPerUnitMinutes { get; set; } + public int SetupTimeMinutes { get; set; } +} + +public class UpdateProductionCycleDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public bool IsDefault { get; set; } + public bool IsActive { get; set; } + public List Phases { get; set; } = new(); +} + +public class UpdateProductionCyclePhaseDto +{ + public int? Id { get; set; } + public int Sequence { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public int WorkCenterId { get; set; } + public int DurationPerUnitMinutes { get; set; } + public int SetupTimeMinutes { get; set; } + public bool IsDeleted { get; set; } +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/ProductionOrderDto.cs b/src/Apollinare.API/Modules/Production/Dtos/ProductionOrderDto.cs new file mode 100644 index 0000000..6ce692f --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/ProductionOrderDto.cs @@ -0,0 +1,55 @@ +using Apollinare.Domain.Entities.Production; + +namespace Apollinare.API.Modules.Production.Dtos; + +public class ProductionOrderDto +{ + public int Id { get; set; } + public string Code { get; set; } = string.Empty; + public int ArticleId { get; set; } + public string ArticleName { get; set; } = string.Empty; + public decimal Quantity { get; set; } + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + public DateTime DueDate { get; set; } + public ProductionOrderStatus Status { get; set; } + public string? Notes { get; set; } + public List Components { get; set; } = new(); + public List Phases { get; set; } = new(); + public int? ParentProductionOrderId { get; set; } + public string? ParentProductionOrderCode { get; set; } + public List ChildOrders { get; set; } = new(); +} + +public class ProductionOrderPhaseDto +{ + public int Id { get; set; } + public int Sequence { get; set; } + public string Name { get; set; } = string.Empty; + public int WorkCenterId { get; set; } + public string WorkCenterName { get; set; } = string.Empty; + public ProductionPhaseStatus Status { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public decimal QuantityCompleted { get; set; } + public decimal QuantityScrapped { get; set; } + public int EstimatedDurationMinutes { get; set; } + public int ActualDurationMinutes { get; set; } +} + +public class ProductionOrderComponentDto +{ + public int Id { get; set; } + public int ArticleId { get; set; } + public string ArticleName { get; set; } = string.Empty; + public decimal RequiredQuantity { get; set; } + public decimal ConsumedQuantity { get; set; } +} + +public class UpdateProductionOrderPhaseDto +{ + public ProductionPhaseStatus Status { get; set; } + public decimal QuantityCompleted { get; set; } + public decimal QuantityScrapped { get; set; } + public int ActualDurationMinutes { get; set; } +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/UpdateBillOfMaterialsDto.cs b/src/Apollinare.API/Modules/Production/Dtos/UpdateBillOfMaterialsDto.cs new file mode 100644 index 0000000..0a4e9b3 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/UpdateBillOfMaterialsDto.cs @@ -0,0 +1,19 @@ +namespace Apollinare.API.Modules.Production.Dtos; + +public class UpdateBillOfMaterialsDto +{ + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public decimal Quantity { get; set; } + public bool IsActive { get; set; } + public List Components { get; set; } = new(); +} + +public class UpdateBillOfMaterialsComponentDto +{ + public int? Id { get; set; } // If null, it's a new component + public int ComponentArticleId { get; set; } + public decimal Quantity { get; set; } + public decimal ScrapPercentage { get; set; } + public bool IsDeleted { get; set; } // To remove components +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/UpdateProductionOrderDto.cs b/src/Apollinare.API/Modules/Production/Dtos/UpdateProductionOrderDto.cs new file mode 100644 index 0000000..8993e18 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/UpdateProductionOrderDto.cs @@ -0,0 +1,12 @@ +using Apollinare.Domain.Entities.Production; + +namespace Apollinare.API.Modules.Production.Dtos; + +public class UpdateProductionOrderDto +{ + public decimal Quantity { get; set; } + public DateTime StartDate { get; set; } + public DateTime DueDate { get; set; } + public string? Notes { get; set; } + public ProductionOrderStatus Status { get; set; } +} diff --git a/src/Apollinare.API/Modules/Production/Dtos/WorkCenterDto.cs b/src/Apollinare.API/Modules/Production/Dtos/WorkCenterDto.cs new file mode 100644 index 0000000..ce3a80a --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Dtos/WorkCenterDto.cs @@ -0,0 +1,27 @@ +namespace Apollinare.API.Modules.Production.Dtos; + +public class WorkCenterDto +{ + public int Id { get; set; } + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public decimal CostPerHour { get; set; } + public bool IsActive { get; set; } +} + +public class CreateWorkCenterDto +{ + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public decimal CostPerHour { get; set; } +} + +public class UpdateWorkCenterDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public decimal CostPerHour { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/Apollinare.API/Modules/Production/Services/IMrpService.cs b/src/Apollinare.API/Modules/Production/Services/IMrpService.cs new file mode 100644 index 0000000..6fba6f6 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Services/IMrpService.cs @@ -0,0 +1,11 @@ +using Apollinare.API.Modules.Production.Dtos; +using Apollinare.Domain.Entities.Production; + +namespace Apollinare.API.Modules.Production.Services; + +public interface IMrpService +{ + Task RunMrpAsync(MrpConfigurationDto config); + Task> GetSuggestionsAsync(bool includeProcessed = false); + Task ProcessSuggestionAsync(int suggestionId); +} diff --git a/src/Apollinare.API/Modules/Production/Services/IProductionService.cs b/src/Apollinare.API/Modules/Production/Services/IProductionService.cs new file mode 100644 index 0000000..8b79af8 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Services/IProductionService.cs @@ -0,0 +1,37 @@ +using Apollinare.API.Modules.Production.Dtos; +using Apollinare.Domain.Entities.Production; + +namespace Apollinare.API.Modules.Production.Services; + +public interface IProductionService +{ + // Bill Of Materials + Task> GetBillOfMaterialsAsync(); + Task GetBillOfMaterialsByIdAsync(int id); + Task CreateBillOfMaterialsAsync(CreateBillOfMaterialsDto dto); + Task UpdateBillOfMaterialsAsync(int id, UpdateBillOfMaterialsDto dto); + Task DeleteBillOfMaterialsAsync(int id); + + // Production Orders + Task> GetProductionOrdersAsync(); + Task GetProductionOrderByIdAsync(int id); + Task CreateProductionOrderAsync(CreateProductionOrderDto dto); + Task UpdateProductionOrderAsync(int id, UpdateProductionOrderDto dto); + Task ChangeProductionOrderStatusAsync(int id, ProductionOrderStatus status); + Task UpdateProductionOrderPhaseAsync(int orderId, int phaseId, UpdateProductionOrderPhaseDto dto); + Task DeleteProductionOrderAsync(int id); + + // Work Centers + Task> GetWorkCentersAsync(); + Task GetWorkCenterByIdAsync(int id); + Task CreateWorkCenterAsync(CreateWorkCenterDto dto); + Task UpdateWorkCenterAsync(int id, UpdateWorkCenterDto dto); + Task DeleteWorkCenterAsync(int id); + + // Production Cycles + Task> GetProductionCyclesAsync(); + Task GetProductionCycleByIdAsync(int id); + Task CreateProductionCycleAsync(CreateProductionCycleDto dto); + Task UpdateProductionCycleAsync(int id, UpdateProductionCycleDto dto); + Task DeleteProductionCycleAsync(int id); +} diff --git a/src/Apollinare.API/Modules/Production/Services/MrpService.cs b/src/Apollinare.API/Modules/Production/Services/MrpService.cs new file mode 100644 index 0000000..cdcf663 --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Services/MrpService.cs @@ -0,0 +1,260 @@ +using Apollinare.Domain.Entities.Production; +using Apollinare.Domain.Entities.Warehouse; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Apollinare.API.Modules.Production.Dtos; + +namespace Apollinare.API.Modules.Production.Services; + +public class MrpService : IMrpService +{ + private readonly AppollinareDbContext _context; + private readonly ILogger _logger; + + public MrpService(AppollinareDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task RunMrpAsync(MrpConfigurationDto config) + { + _logger.LogInformation("Starting Multi-Level MRP Run with config: {@Config}", config); + + // 1. Clear existing unprocessed suggestions + var oldSuggestions = await _context.MrpSuggestions + .Where(s => !s.IsProcessed) + .ToListAsync(); + _context.MrpSuggestions.RemoveRange(oldSuggestions); + await _context.SaveChangesAsync(); + + // 2. Load Data + + // 2.0 Article Details (Safety Stock, Lead Time) + var articleDetails = await _context.WarehouseArticles + .Select(a => new { a.Id, a.MinimumStock, a.LeadTimeDays }) + .ToDictionaryAsync(a => a.Id); + + // 2.1 Stock Levels (Supply) + var stockQuery = _context.StockLevels.AsQueryable(); + if (config.WarehouseIds != null && config.WarehouseIds.Any()) + { + stockQuery = stockQuery.Where(s => config.WarehouseIds.Contains(s.WarehouseId)); + } + + var stockLevels = await stockQuery + .GroupBy(s => s.ArticleId) + .Select(g => new { ArticleId = g.Key, Quantity = g.Sum(s => s.Quantity) }) + .ToDictionaryAsync(x => x.ArticleId, x => x.Quantity); + + // 2.2 Incoming Purchase Orders (Supply) + var incomingPurchases = await _context.PurchaseOrderLines + .Include(l => l.PurchaseOrder) + .Where(l => l.PurchaseOrder.Status == Domain.Entities.Purchases.PurchaseOrderStatus.Confirmed || + l.PurchaseOrder.Status == Domain.Entities.Purchases.PurchaseOrderStatus.PartiallyReceived) + .GroupBy(l => l.WarehouseArticleId) + .Select(g => new { ArticleId = g.Key, Quantity = g.Sum(l => l.Quantity - l.ReceivedQuantity) }) + .ToListAsync(); + + // 2.3 Incoming Production Orders (Supply for Parent, Demand for Components) + var incomingProduction = await _context.ProductionOrders + .Where(o => o.Status == ProductionOrderStatus.Planned || + o.Status == ProductionOrderStatus.Released || + o.Status == ProductionOrderStatus.InProgress) + .ToListAsync(); + + // 2.4 Sales Orders (Independent Demand) + var salesDemand = new List(); + if (config.IncludeSalesOrders) + { + var salesQuery = _context.SalesOrderLines + .Include(l => l.SalesOrder) + .Where(l => l.SalesOrder.Status == Domain.Entities.Sales.SalesOrderStatus.Confirmed || + l.SalesOrder.Status == Domain.Entities.Sales.SalesOrderStatus.PartiallyShipped); + + var salesList = await salesQuery + .GroupBy(l => l.WarehouseArticleId) + .Select(g => new { ArticleId = g.Key, Quantity = g.Sum(l => l.Quantity) }) + .ToListAsync(); + + salesDemand.AddRange(salesList); + } + + // 2.5 BOMs (Structure) + var boms = await _context.BillOfMaterials + .Include(b => b.Components) + .Where(b => b.IsActive) + .ToListAsync(); + + var bomDictionary = boms.GroupBy(b => b.ArticleId).ToDictionary(g => g.Key, g => g.First()); + + // 3. Initialize In-Memory State + var stockOnHand = new Dictionary(stockLevels); + + // Add Incoming Purchases to Stock + foreach (var p in incomingPurchases) + { + if (!stockOnHand.ContainsKey(p.ArticleId)) stockOnHand[p.ArticleId] = 0; + stockOnHand[p.ArticleId] += p.Quantity; + } + + // Add Incoming Production (Parent Items) to Stock + foreach (var p in incomingProduction) + { + if (!stockOnHand.ContainsKey(p.ArticleId)) stockOnHand[p.ArticleId] = 0; + stockOnHand[p.ArticleId] += p.Quantity; + } + + // 4. Process Demand + var suggestions = new List(); + var calculationDate = DateTime.UtcNow; + + // Helper function for recursive processing + void ProcessRequirement(int articleId, decimal qtyNeeded, string sourceReason, DateTime neededByDate) + { + if (qtyNeeded <= 0) return; + + // Safety Stock Logic + decimal safetyStock = 0; + int leadTimeDays = 0; + if (articleDetails.TryGetValue(articleId, out var details)) + { + if (config.IncludeSafetyStock && details.MinimumStock.HasValue) + { + safetyStock = details.MinimumStock.Value; + } + leadTimeDays = details.LeadTimeDays ?? 0; + } + + decimal currentStock = stockOnHand.ContainsKey(articleId) ? stockOnHand[articleId] : 0; + decimal availableForDemand = currentStock - safetyStock; + + if (availableForDemand >= qtyNeeded) + { + // Demand met by stock + stockOnHand[articleId] = currentStock - qtyNeeded; + } + else + { + // Consume remaining available stock + decimal toConsume = Math.Max(0, availableForDemand); + stockOnHand[articleId] = currentStock - toConsume; + + var netRequirement = qtyNeeded - availableForDemand; + + // Create Suggestion + bool hasBom = bomDictionary.ContainsKey(articleId); + var type = hasBom ? MrpSuggestionType.Production : MrpSuggestionType.Purchase; + + var orderDate = neededByDate.AddDays(-leadTimeDays); + if (orderDate < DateTime.UtcNow) orderDate = DateTime.UtcNow; + + suggestions.Add(new MrpSuggestion + { + CalculationDate = calculationDate, + ArticleId = articleId, + Type = type, + Quantity = netRequirement, + SuggestionDate = orderDate, + Reason = $"{sourceReason} (Net: {netRequirement:F2})", + IsProcessed = false + }); + + // Explode BOM if Production + if (hasBom) + { + var bom = bomDictionary[articleId]; + foreach (var comp in bom.Components) + { + var compQtyNeeded = netRequirement * comp.Quantity; + if (comp.ScrapPercentage > 0) + { + compQtyNeeded = compQtyNeeded * (1 + comp.ScrapPercentage / 100); + } + + ProcessRequirement(comp.ComponentArticleId, compQtyNeeded, $"Ref: {articleId}", orderDate); + } + } + } + } + + // 4.1 Process Sales Orders + foreach (var demand in salesDemand) + { + ProcessRequirement(demand.ArticleId, demand.Quantity, "Sales Order", DateTime.UtcNow); + } + + // 4.2 Process Existing Production Order Components + var existingOrderComponents = await _context.ProductionOrderComponents + .Include(c => c.ProductionOrder) + .Where(c => c.ProductionOrder.Status == ProductionOrderStatus.Planned || + c.ProductionOrder.Status == ProductionOrderStatus.Released || + c.ProductionOrder.Status == ProductionOrderStatus.InProgress) + .ToListAsync(); + + foreach (var comp in existingOrderComponents) + { + var remainingNeeded = comp.RequiredQuantity - comp.ConsumedQuantity; + if (remainingNeeded > 0) + { + var neededDate = comp.ProductionOrder.StartDate; + ProcessRequirement(comp.ArticleId, remainingNeeded, $"Prod Order {comp.ProductionOrder.Code}", neededDate); + } + } + + // 5. Save Suggestions + if (suggestions.Any()) + { + var groupedSuggestions = suggestions + .GroupBy(s => new { s.ArticleId, s.Type }) + .Select(g => new MrpSuggestion + { + CalculationDate = calculationDate, + ArticleId = g.Key.ArticleId, + Type = g.Key.Type, + Quantity = g.Sum(s => s.Quantity), + SuggestionDate = g.Min(s => s.SuggestionDate), + Reason = "Aggregated Demand", + IsProcessed = false + }) + .ToList(); + + _context.MrpSuggestions.AddRange(groupedSuggestions); + await _context.SaveChangesAsync(); + } + + _logger.LogInformation("MRP Run completed. Generated {Count} suggestions.", suggestions.Count); + } + + public async Task> GetSuggestionsAsync(bool includeProcessed = false) + { + var query = _context.MrpSuggestions + .Include(s => s.Article) + .AsQueryable(); + + if (!includeProcessed) + { + query = query.Where(s => !s.IsProcessed); + } + + return await query.OrderBy(s => s.SuggestionDate).ToListAsync(); + } + + public async Task ProcessSuggestionAsync(int suggestionId) + { + var suggestion = await _context.MrpSuggestions.FindAsync(suggestionId); + if (suggestion == null) return; + + // Logic to auto-create orders based on suggestion + if (suggestion.Type == MrpSuggestionType.Production) + { + // Create Production Order + // We need to call ProductionService, but circular dependency might be an issue if we inject it. + // Better to keep this logic in Controller or have a separate Orchestrator. + // For now, just mark as processed. The Controller likely handles the actual creation. + } + + suggestion.IsProcessed = true; + await _context.SaveChangesAsync(); + } +} diff --git a/src/Apollinare.API/Modules/Production/Services/ProductionService.cs b/src/Apollinare.API/Modules/Production/Services/ProductionService.cs new file mode 100644 index 0000000..8b716ff --- /dev/null +++ b/src/Apollinare.API/Modules/Production/Services/ProductionService.cs @@ -0,0 +1,803 @@ +using Apollinare.API.Modules.Production.Dtos; +using Apollinare.API.Modules.Warehouse.Services; +using Apollinare.Domain.Entities.Production; +using Apollinare.Domain.Entities.Warehouse; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Apollinare.API.Modules.Production.Services; + +public class ProductionService : IProductionService +{ + private readonly AppollinareDbContext _context; + private readonly IWarehouseService _warehouseService; + + public ProductionService(AppollinareDbContext context, IWarehouseService warehouseService) + { + _context = context; + _warehouseService = warehouseService; + } + + // =============================================== + // BILL OF MATERIALS + // =============================================== + + public async Task> GetBillOfMaterialsAsync() + { + return await _context.BillOfMaterials + .Include(b => b.Article) + .Include(b => b.Components) + .ThenInclude(c => c.ComponentArticle) + .Where(b => b.IsActive) + .Select(b => new BillOfMaterialsDto + { + Id = b.Id, + Name = b.Name, + Description = b.Description, + ArticleId = b.ArticleId, + ArticleName = b.Article.Description, + Quantity = b.Quantity, + IsActive = b.IsActive, + Components = b.Components.Select(c => new BillOfMaterialsComponentDto + { + Id = c.Id, + ComponentArticleId = c.ComponentArticleId, + ComponentArticleName = c.ComponentArticle.Description, + Quantity = c.Quantity, + ScrapPercentage = c.ScrapPercentage + }).ToList() + }) + .ToListAsync(); + } + + public async Task GetBillOfMaterialsByIdAsync(int id) + { + var bom = await _context.BillOfMaterials + .Include(b => b.Article) + .Include(b => b.Components) + .ThenInclude(c => c.ComponentArticle) + .FirstOrDefaultAsync(b => b.Id == id); + + if (bom == null) return null; + + return new BillOfMaterialsDto + { + Id = bom.Id, + Name = bom.Name, + Description = bom.Description, + ArticleId = bom.ArticleId, + ArticleName = bom.Article.Description, + Quantity = bom.Quantity, + IsActive = bom.IsActive, + Components = bom.Components.Select(c => new BillOfMaterialsComponentDto + { + Id = c.Id, + ComponentArticleId = c.ComponentArticleId, + ComponentArticleName = c.ComponentArticle.Description, + Quantity = c.Quantity, + ScrapPercentage = c.ScrapPercentage + }).ToList() + }; + } + + public async Task CreateBillOfMaterialsAsync(CreateBillOfMaterialsDto dto) + { + var bom = new BillOfMaterials + { + Name = dto.Name, + Description = dto.Description, + ArticleId = dto.ArticleId, + Quantity = dto.Quantity, + IsActive = true + }; + + foreach (var compDto in dto.Components) + { + bom.Components.Add(new BillOfMaterialsComponent + { + ComponentArticleId = compDto.ComponentArticleId, + Quantity = compDto.Quantity, + ScrapPercentage = compDto.ScrapPercentage + }); + } + + _context.BillOfMaterials.Add(bom); + await _context.SaveChangesAsync(); + + return await GetBillOfMaterialsByIdAsync(bom.Id) ?? throw new InvalidOperationException("Failed to retrieve created BOM"); + } + + public async Task UpdateBillOfMaterialsAsync(int id, UpdateBillOfMaterialsDto dto) + { + var bom = await _context.BillOfMaterials + .Include(b => b.Components) + .FirstOrDefaultAsync(b => b.Id == id); + + if (bom == null) throw new KeyNotFoundException($"BOM with ID {id} not found"); + + bom.Name = dto.Name; + bom.Description = dto.Description; + bom.Quantity = dto.Quantity; + bom.IsActive = dto.IsActive; + + // Update components + foreach (var compDto in dto.Components) + { + if (compDto.IsDeleted) + { + if (compDto.Id.HasValue) + { + var compToDelete = bom.Components.FirstOrDefault(c => c.Id == compDto.Id.Value); + if (compToDelete != null) + { + _context.BillOfMaterialsComponents.Remove(compToDelete); + } + } + } + else if (compDto.Id.HasValue) + { + var compToUpdate = bom.Components.FirstOrDefault(c => c.Id == compDto.Id.Value); + if (compToUpdate != null) + { + compToUpdate.ComponentArticleId = compDto.ComponentArticleId; + compToUpdate.Quantity = compDto.Quantity; + compToUpdate.ScrapPercentage = compDto.ScrapPercentage; + } + } + else + { + // New component + bom.Components.Add(new BillOfMaterialsComponent + { + ComponentArticleId = compDto.ComponentArticleId, + Quantity = compDto.Quantity, + ScrapPercentage = compDto.ScrapPercentage + }); + } + } + + await _context.SaveChangesAsync(); + return await GetBillOfMaterialsByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve updated BOM"); + } + + public async Task DeleteBillOfMaterialsAsync(int id) + { + var bom = await _context.BillOfMaterials.FindAsync(id); + if (bom != null) + { + _context.BillOfMaterials.Remove(bom); + await _context.SaveChangesAsync(); + } + } + + // =============================================== + // PRODUCTION ORDERS + // =============================================== + + public async Task> GetProductionOrdersAsync() + { + return await _context.ProductionOrders + .Include(o => o.Article) + .Include(o => o.Components) + .ThenInclude(c => c.Article) + .Include(o => o.Phases) + .ThenInclude(p => p.WorkCenter) + .OrderByDescending(o => o.StartDate) + .Select(o => new ProductionOrderDto + { + Id = o.Id, + Code = o.Code, + ArticleId = o.ArticleId, + ArticleName = o.Article.Description, + Quantity = o.Quantity, + StartDate = o.StartDate, + EndDate = o.EndDate, + DueDate = o.DueDate, + Status = o.Status, + Notes = o.Notes, + Components = o.Components.Select(c => new ProductionOrderComponentDto + { + Id = c.Id, + ArticleId = c.ArticleId, + ArticleName = c.Article.Description, + RequiredQuantity = c.RequiredQuantity, + ConsumedQuantity = c.ConsumedQuantity + }).ToList(), + Phases = o.Phases.OrderBy(p => p.Sequence).Select(p => new ProductionOrderPhaseDto + { + Id = p.Id, + Sequence = p.Sequence, + Name = p.Name, + WorkCenterId = p.WorkCenterId, + WorkCenterName = p.WorkCenter.Name, + Status = p.Status, + StartDate = p.StartDate, + EndDate = p.EndDate, + QuantityCompleted = p.QuantityCompleted, + QuantityScrapped = p.QuantityScrapped, + EstimatedDurationMinutes = p.EstimatedDurationMinutes, + ActualDurationMinutes = p.ActualDurationMinutes + }).ToList(), + ParentProductionOrderId = o.ParentProductionOrderId, + ParentProductionOrderCode = o.ParentProductionOrder != null ? o.ParentProductionOrder.Code : null + }) + .ToListAsync(); + } + + public async Task GetProductionOrderByIdAsync(int id) + { + var order = await _context.ProductionOrders + .Include(o => o.Article) + .Include(o => o.Components) + .ThenInclude(c => c.Article) + .Include(o => o.Phases) + .ThenInclude(p => p.WorkCenter) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order == null) return null; + + return new ProductionOrderDto + { + Id = order.Id, + Code = order.Code, + ArticleId = order.ArticleId, + ArticleName = order.Article.Description, + Quantity = order.Quantity, + StartDate = order.StartDate, + EndDate = order.EndDate, + DueDate = order.DueDate, + Status = order.Status, + Notes = order.Notes, + Components = order.Components.Select(c => new ProductionOrderComponentDto + { + Id = c.Id, + ArticleId = c.ArticleId, + ArticleName = c.Article.Description, + RequiredQuantity = c.RequiredQuantity, + ConsumedQuantity = c.ConsumedQuantity + }).ToList(), + Phases = order.Phases.OrderBy(p => p.Sequence).Select(p => new ProductionOrderPhaseDto + { + Id = p.Id, + Sequence = p.Sequence, + Name = p.Name, + WorkCenterId = p.WorkCenterId, + WorkCenterName = p.WorkCenter.Name, + Status = p.Status, + StartDate = p.StartDate, + EndDate = p.EndDate, + QuantityCompleted = p.QuantityCompleted, + QuantityScrapped = p.QuantityScrapped, + EstimatedDurationMinutes = p.EstimatedDurationMinutes, + ActualDurationMinutes = p.ActualDurationMinutes + }).ToList(), + ParentProductionOrderId = order.ParentProductionOrderId, + ParentProductionOrderCode = order.ParentProductionOrder?.Code, + ChildOrders = order.ChildProductionOrders.Select(c => new ProductionOrderDto + { + Id = c.Id, + Code = c.Code, + ArticleId = c.ArticleId, + ArticleName = c.Article.Description, + Quantity = c.Quantity, + StartDate = c.StartDate, + DueDate = c.DueDate, + Status = c.Status + }).ToList() + }; + } + + public async Task CreateProductionOrderAsync(CreateProductionOrderDto dto) + { + var order = new ProductionOrder + { + Code = $"PO-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString().Substring(0, 4).ToUpper()}", // Simple code generation + ArticleId = dto.ArticleId, + Quantity = dto.Quantity, + StartDate = dto.StartDate, + DueDate = dto.DueDate, + Status = ProductionOrderStatus.Draft, + Notes = dto.Notes, + ParentProductionOrderId = dto.ParentProductionOrderId + }; + + // If BOM is provided, copy components + if (dto.BillOfMaterialsId.HasValue) + { + var bom = await _context.BillOfMaterials + .Include(b => b.Components) + .FirstOrDefaultAsync(b => b.Id == dto.BillOfMaterialsId.Value); + + if (bom != null) + { + // Calculate ratio based on BOM quantity vs Order quantity + var ratio = dto.Quantity / bom.Quantity; + + foreach (var comp in bom.Components) + { + order.Components.Add(new ProductionOrderComponent + { + ArticleId = comp.ComponentArticleId, + RequiredQuantity = comp.Quantity * ratio, + ConsumedQuantity = 0 + }); + } + } + } + + // Copy default production cycle phases + var defaultCycle = await _context.ProductionCycles + .Include(c => c.Phases) + .FirstOrDefaultAsync(c => c.ArticleId == dto.ArticleId && c.IsDefault && c.IsActive); + + if (defaultCycle != null) + { + foreach (var phase in defaultCycle.Phases) + { + order.Phases.Add(new ProductionOrderPhase + { + Sequence = phase.Sequence, + Name = phase.Name, + WorkCenterId = phase.WorkCenterId, + Status = ProductionPhaseStatus.Pending, + EstimatedDurationMinutes = phase.SetupTimeMinutes + (phase.DurationPerUnitMinutes * (int)dto.Quantity), + QuantityCompleted = 0, + QuantityScrapped = 0 + }); + } + } + + _context.ProductionOrders.Add(order); + await _context.SaveChangesAsync(); + + // Recursively create child orders if requested + if (dto.CreateChildOrders && order.Components.Any()) + { + foreach (var comp in order.Components) + { + // Check if component has a BOM (is manufactured) + var compBom = await _context.BillOfMaterials + .FirstOrDefaultAsync(b => b.ArticleId == comp.ArticleId && b.IsActive); + + if (compBom != null) + { + // Create child order + var childDto = new CreateProductionOrderDto + { + ArticleId = comp.ArticleId, + Quantity = comp.RequiredQuantity, + StartDate = dto.StartDate.AddDays(-1), // Simple scheduling: start 1 day earlier + DueDate = dto.StartDate, // Must be ready by parent start + Notes = $"Auto-generated for Parent Order {order.Code}", + BillOfMaterialsId = compBom.Id, + CreateChildOrders = true, // Recursive + ParentProductionOrderId = order.Id // Link to parent + }; + + await CreateProductionOrderAsync(childDto); + } + } + } + + return await GetProductionOrderByIdAsync(order.Id) ?? throw new InvalidOperationException("Failed to retrieve created order"); + } + + public async Task UpdateProductionOrderAsync(int id, UpdateProductionOrderDto dto) + { + var order = await _context.ProductionOrders.FindAsync(id); + if (order == null) throw new KeyNotFoundException($"Order with ID {id} not found"); + + if (order.Status == ProductionOrderStatus.Completed || order.Status == ProductionOrderStatus.Cancelled) + { + throw new InvalidOperationException("Cannot update completed or cancelled orders"); + } + + order.Quantity = dto.Quantity; + order.StartDate = dto.StartDate; + order.DueDate = dto.DueDate; + order.Notes = dto.Notes; + + // Status change logic could be complex, for now just allow simple updates if not final + // Ideally status change should be a separate method (which it is) + + await _context.SaveChangesAsync(); + return await GetProductionOrderByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve updated order"); + } + + public async Task ChangeProductionOrderStatusAsync(int id, ProductionOrderStatus status) + { + var order = await _context.ProductionOrders + .Include(o => o.Components) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order == null) throw new KeyNotFoundException($"Order with ID {id} not found"); + + // Simple state machine check + if (order.Status == ProductionOrderStatus.Completed) + throw new InvalidOperationException("Order is already completed"); + + if (status == ProductionOrderStatus.Completed) + { + var defaultWarehouseId = await GetDefaultWarehouseIdAsync(); + + // Get reasons + var prodReason = await _context.MovementReasons.FirstOrDefaultAsync(r => r.Code == "PROD"); + var consReason = await _context.MovementReasons.FirstOrDefaultAsync(r => r.Code == "CONS"); + + // 1. Create Inbound Movement for Finished Good + var inboundMovement = new StockMovement + { + MovementDate = DateTime.UtcNow, + Type = MovementType.Production, // Inbound from Production + Status = MovementStatus.Draft, // Must be Draft to be confirmed + ReasonId = prodReason?.Id, + ExternalReference = order.Code, + ExternalDocumentType = ExternalDocumentType.ProductionOrder, + Notes = $"Production Order {order.Code} Completed", + DestinationWarehouseId = defaultWarehouseId + }; + + inboundMovement.Lines.Add(new StockMovementLine + { + ArticleId = order.ArticleId, + Quantity = order.Quantity, + LineNumber = 1 + }); + + await _warehouseService.CreateMovementAsync(inboundMovement); + await _warehouseService.ConfirmMovementAsync(inboundMovement.Id); + + // 2. Create Outbound Movement for Components (Consumption) + if (order.Components.Any()) + { + var outboundMovement = new StockMovement + { + MovementDate = DateTime.UtcNow, + Type = MovementType.Consumption, // Outbound for Production + Status = MovementStatus.Draft, // Must be Draft to be confirmed + ReasonId = consReason?.Id, + ExternalReference = order.Code, + ExternalDocumentType = ExternalDocumentType.ProductionOrder, + Notes = $"Consumption for Production Order {order.Code}", + SourceWarehouseId = defaultWarehouseId + }; + + int lineNum = 1; + foreach (var comp in order.Components) + { + outboundMovement.Lines.Add(new StockMovementLine + { + ArticleId = comp.ArticleId, + Quantity = comp.RequiredQuantity, // Consuming required quantity + LineNumber = lineNum++ + }); + + // Update consumed quantity on the order component + comp.ConsumedQuantity = comp.RequiredQuantity; + } + + await _warehouseService.CreateMovementAsync(outboundMovement); + await _warehouseService.ConfirmMovementAsync(outboundMovement.Id); + } + + order.EndDate = DateTime.Now; + } + + order.Status = status; + await _context.SaveChangesAsync(); + + return await GetProductionOrderByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve updated order"); + } + + public async Task UpdateProductionOrderPhaseAsync(int orderId, int phaseId, UpdateProductionOrderPhaseDto dto) + { + var phase = await _context.ProductionOrderPhases + .FirstOrDefaultAsync(p => p.Id == phaseId && p.ProductionOrderId == orderId); + + if (phase == null) throw new KeyNotFoundException($"Phase with ID {phaseId} not found in order {orderId}"); + + phase.Status = dto.Status; + phase.QuantityCompleted = dto.QuantityCompleted; + phase.QuantityScrapped = dto.QuantityScrapped; + phase.ActualDurationMinutes = dto.ActualDurationMinutes; + + if (dto.Status == ProductionPhaseStatus.InProgress && phase.StartDate == null) + { + phase.StartDate = DateTime.Now; + } + else if (dto.Status == ProductionPhaseStatus.Completed && phase.EndDate == null) + { + phase.EndDate = DateTime.Now; + } + + await _context.SaveChangesAsync(); + return await GetProductionOrderByIdAsync(orderId) ?? throw new InvalidOperationException("Failed to retrieve updated order"); + } + + public async Task DeleteProductionOrderAsync(int id) + { + var order = await _context.ProductionOrders.FindAsync(id); + if (order != null) + { + if (order.Status != ProductionOrderStatus.Draft) + throw new InvalidOperationException("Only draft orders can be deleted"); + + _context.ProductionOrders.Remove(order); + await _context.SaveChangesAsync(); + } + } + + private async Task GetDefaultWarehouseIdAsync() + { + var wh = await _warehouseService.GetDefaultWarehouseAsync(); + return wh?.Id ?? throw new InvalidOperationException("No default warehouse found"); + } + + // =============================================== + // WORK CENTERS + // =============================================== + + public async Task> GetWorkCentersAsync() + { + return await _context.WorkCenters + .Where(w => w.IsActive) + .Select(w => new WorkCenterDto + { + Id = w.Id, + Code = w.Code, + Name = w.Name, + Description = w.Description, + CostPerHour = w.CostPerHour, + IsActive = w.IsActive + }) + .ToListAsync(); + } + + public async Task GetWorkCenterByIdAsync(int id) + { + var w = await _context.WorkCenters.FindAsync(id); + if (w == null) return null; + + return new WorkCenterDto + { + Id = w.Id, + Code = w.Code, + Name = w.Name, + Description = w.Description, + CostPerHour = w.CostPerHour, + IsActive = w.IsActive + }; + } + + public async Task CreateWorkCenterAsync(CreateWorkCenterDto dto) + { + if (await _context.WorkCenters.AnyAsync(w => w.Code == dto.Code)) + throw new InvalidOperationException($"Work center with code {dto.Code} already exists"); + + var workCenter = new WorkCenter + { + Code = dto.Code, + Name = dto.Name, + Description = dto.Description, + CostPerHour = dto.CostPerHour, + IsActive = true + }; + + _context.WorkCenters.Add(workCenter); + await _context.SaveChangesAsync(); + + return await GetWorkCenterByIdAsync(workCenter.Id) ?? throw new InvalidOperationException("Failed to retrieve created work center"); + } + + public async Task UpdateWorkCenterAsync(int id, UpdateWorkCenterDto dto) + { + var workCenter = await _context.WorkCenters.FindAsync(id); + if (workCenter == null) throw new KeyNotFoundException($"Work center with ID {id} not found"); + + workCenter.Name = dto.Name; + workCenter.Description = dto.Description; + workCenter.CostPerHour = dto.CostPerHour; + workCenter.IsActive = dto.IsActive; + + await _context.SaveChangesAsync(); + return await GetWorkCenterByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve updated work center"); + } + + public async Task DeleteWorkCenterAsync(int id) + { + var workCenter = await _context.WorkCenters.FindAsync(id); + if (workCenter != null) + { + // Check if used in any cycle or order phase + if (await _context.ProductionCyclePhases.AnyAsync(p => p.WorkCenterId == id)) + throw new InvalidOperationException("Cannot delete work center used in production cycles"); + + if (await _context.ProductionOrderPhases.AnyAsync(p => p.WorkCenterId == id)) + throw new InvalidOperationException("Cannot delete work center used in production orders"); + + _context.WorkCenters.Remove(workCenter); + await _context.SaveChangesAsync(); + } + } + + // =============================================== + // PRODUCTION CYCLES + // =============================================== + + public async Task> GetProductionCyclesAsync() + { + return await _context.ProductionCycles + .Include(c => c.Article) + .Include(c => c.Phases) + .ThenInclude(p => p.WorkCenter) + .Where(c => c.IsActive) + .Select(c => new ProductionCycleDto + { + Id = c.Id, + Name = c.Name, + Description = c.Description, + ArticleId = c.ArticleId, + ArticleName = c.Article.Description, + IsDefault = c.IsDefault, + IsActive = c.IsActive, + Phases = c.Phases.OrderBy(p => p.Sequence).Select(p => new ProductionCyclePhaseDto + { + Id = p.Id, + Sequence = p.Sequence, + Name = p.Name, + Description = p.Description, + WorkCenterId = p.WorkCenterId, + WorkCenterName = p.WorkCenter.Name, + DurationPerUnitMinutes = p.DurationPerUnitMinutes, + SetupTimeMinutes = p.SetupTimeMinutes + }).ToList() + }) + .ToListAsync(); + } + + public async Task GetProductionCycleByIdAsync(int id) + { + var c = await _context.ProductionCycles + .Include(c => c.Article) + .Include(c => c.Phases) + .ThenInclude(p => p.WorkCenter) + .FirstOrDefaultAsync(x => x.Id == id); + + if (c == null) return null; + + return new ProductionCycleDto + { + Id = c.Id, + Name = c.Name, + Description = c.Description, + ArticleId = c.ArticleId, + ArticleName = c.Article.Description, + IsDefault = c.IsDefault, + IsActive = c.IsActive, + Phases = c.Phases.OrderBy(p => p.Sequence).Select(p => new ProductionCyclePhaseDto + { + Id = p.Id, + Sequence = p.Sequence, + Name = p.Name, + Description = p.Description, + WorkCenterId = p.WorkCenterId, + WorkCenterName = p.WorkCenter.Name, + DurationPerUnitMinutes = p.DurationPerUnitMinutes, + SetupTimeMinutes = p.SetupTimeMinutes + }).ToList() + }; + } + + public async Task CreateProductionCycleAsync(CreateProductionCycleDto dto) + { + var cycle = new ProductionCycle + { + Name = dto.Name, + Description = dto.Description, + ArticleId = dto.ArticleId, + IsDefault = dto.IsDefault, + IsActive = true + }; + + if (dto.IsDefault) + { + // Unset other defaults for this article + var defaults = await _context.ProductionCycles + .Where(c => c.ArticleId == dto.ArticleId && c.IsDefault) + .ToListAsync(); + foreach (var d in defaults) d.IsDefault = false; + } + + foreach (var phaseDto in dto.Phases) + { + cycle.Phases.Add(new ProductionCyclePhase + { + Sequence = phaseDto.Sequence, + Name = phaseDto.Name, + Description = phaseDto.Description, + WorkCenterId = phaseDto.WorkCenterId, + DurationPerUnitMinutes = phaseDto.DurationPerUnitMinutes, + SetupTimeMinutes = phaseDto.SetupTimeMinutes + }); + } + + _context.ProductionCycles.Add(cycle); + await _context.SaveChangesAsync(); + + return await GetProductionCycleByIdAsync(cycle.Id) ?? throw new InvalidOperationException("Failed to retrieve created cycle"); + } + + public async Task UpdateProductionCycleAsync(int id, UpdateProductionCycleDto dto) + { + var cycle = await _context.ProductionCycles + .Include(c => c.Phases) + .FirstOrDefaultAsync(c => c.Id == id); + + if (cycle == null) throw new KeyNotFoundException($"Cycle with ID {id} not found"); + + cycle.Name = dto.Name; + cycle.Description = dto.Description; + cycle.IsActive = dto.IsActive; + + if (dto.IsDefault && !cycle.IsDefault) + { + // Unset other defaults + var defaults = await _context.ProductionCycles + .Where(c => c.ArticleId == cycle.ArticleId && c.IsDefault && c.Id != id) + .ToListAsync(); + foreach (var d in defaults) d.IsDefault = false; + } + cycle.IsDefault = dto.IsDefault; + + // Update phases + foreach (var phaseDto in dto.Phases) + { + if (phaseDto.IsDeleted) + { + if (phaseDto.Id.HasValue) + { + var phaseToDelete = cycle.Phases.FirstOrDefault(p => p.Id == phaseDto.Id.Value); + if (phaseToDelete != null) _context.ProductionCyclePhases.Remove(phaseToDelete); + } + } + else if (phaseDto.Id.HasValue) + { + var phaseToUpdate = cycle.Phases.FirstOrDefault(p => p.Id == phaseDto.Id.Value); + if (phaseToUpdate != null) + { + phaseToUpdate.Sequence = phaseDto.Sequence; + phaseToUpdate.Name = phaseDto.Name; + phaseToUpdate.Description = phaseDto.Description; + phaseToUpdate.WorkCenterId = phaseDto.WorkCenterId; + phaseToUpdate.DurationPerUnitMinutes = phaseDto.DurationPerUnitMinutes; + phaseToUpdate.SetupTimeMinutes = phaseDto.SetupTimeMinutes; + } + } + else + { + cycle.Phases.Add(new ProductionCyclePhase + { + Sequence = phaseDto.Sequence, + Name = phaseDto.Name, + Description = phaseDto.Description, + WorkCenterId = phaseDto.WorkCenterId, + DurationPerUnitMinutes = phaseDto.DurationPerUnitMinutes, + SetupTimeMinutes = phaseDto.SetupTimeMinutes + }); + } + } + + await _context.SaveChangesAsync(); + return await GetProductionCycleByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve updated cycle"); + } + + public async Task DeleteProductionCycleAsync(int id) + { + var cycle = await _context.ProductionCycles.FindAsync(id); + if (cycle != null) + { + _context.ProductionCycles.Remove(cycle); + await _context.SaveChangesAsync(); + } + } +} diff --git a/src/Apollinare.API/Modules/Purchases/Controllers/PurchaseOrdersController.cs b/src/Apollinare.API/Modules/Purchases/Controllers/PurchaseOrdersController.cs new file mode 100644 index 0000000..1cae832 --- /dev/null +++ b/src/Apollinare.API/Modules/Purchases/Controllers/PurchaseOrdersController.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Apollinare.API.Modules.Purchases.Dtos; +using Apollinare.API.Modules.Purchases.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Modules.Purchases.Controllers; + +[ApiController] +[Route("api/purchases/orders")] +public class PurchaseOrdersController : ControllerBase +{ + private readonly PurchaseService _service; + + public PurchaseOrdersController(PurchaseService service) + { + _service = service; + } + + [HttpGet] + public async Task>> GetAll() + { + return Ok(await _service.GetAllAsync()); + } + + [HttpGet("{id}")] + public async Task> GetById(int id) + { + var order = await _service.GetByIdAsync(id); + if (order == null) return NotFound(); + return Ok(order); + } + + [HttpPost] + public async Task> Create(CreatePurchaseOrderDto dto) + { + var order = await _service.CreateAsync(dto); + return CreatedAtAction(nameof(GetById), new { id = order.Id }, order); + } + + [HttpPut("{id}")] + public async Task> Update(int id, UpdatePurchaseOrderDto dto) + { + try + { + var order = await _service.UpdateAsync(id, dto); + if (order == null) return NotFound(); + return Ok(order); + } + catch (System.InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("{id}")] + public async Task Delete(int id) + { + try + { + var result = await _service.DeleteAsync(id); + if (!result) return NotFound(); + return NoContent(); + } + catch (System.InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("{id}/confirm")] + public async Task> Confirm(int id) + { + try + { + var order = await _service.ConfirmOrderAsync(id); + return Ok(order); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (System.InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("{id}/receive")] + public async Task> Receive(int id) + { + try + { + var order = await _service.ReceiveOrderAsync(id); + return Ok(order); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (System.InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } +} diff --git a/src/Apollinare.API/Modules/Purchases/Controllers/SuppliersController.cs b/src/Apollinare.API/Modules/Purchases/Controllers/SuppliersController.cs new file mode 100644 index 0000000..d996ff6 --- /dev/null +++ b/src/Apollinare.API/Modules/Purchases/Controllers/SuppliersController.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Apollinare.API.Modules.Purchases.Dtos; +using Apollinare.API.Modules.Purchases.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Modules.Purchases.Controllers; + +[ApiController] +[Route("api/purchases/suppliers")] +public class SuppliersController : ControllerBase +{ + private readonly SupplierService _service; + + public SuppliersController(SupplierService service) + { + _service = service; + } + + [HttpGet] + public async Task>> GetAll() + { + return Ok(await _service.GetAllAsync()); + } + + [HttpGet("{id}")] + public async Task> GetById(int id) + { + var supplier = await _service.GetByIdAsync(id); + if (supplier == null) return NotFound(); + return Ok(supplier); + } + + [HttpPost] + public async Task> Create(CreateSupplierDto dto) + { + var supplier = await _service.CreateAsync(dto); + return CreatedAtAction(nameof(GetById), new { id = supplier.Id }, supplier); + } + + [HttpPut("{id}")] + public async Task> Update(int id, UpdateSupplierDto dto) + { + var supplier = await _service.UpdateAsync(id, dto); + if (supplier == null) return NotFound(); + return Ok(supplier); + } + + [HttpDelete("{id}")] + public async Task Delete(int id) + { + var result = await _service.DeleteAsync(id); + if (!result) return NotFound(); + return NoContent(); + } +} diff --git a/src/Apollinare.API/Modules/Purchases/Dtos/PurchaseOrderDtos.cs b/src/Apollinare.API/Modules/Purchases/Dtos/PurchaseOrderDtos.cs new file mode 100644 index 0000000..40504ff --- /dev/null +++ b/src/Apollinare.API/Modules/Purchases/Dtos/PurchaseOrderDtos.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using Apollinare.Domain.Entities.Purchases; + +namespace Apollinare.API.Modules.Purchases.Dtos; + +public class PurchaseOrderDto +{ + public int Id { get; set; } + public string OrderNumber { get; set; } = string.Empty; + public DateTime OrderDate { get; set; } + public DateTime? ExpectedDeliveryDate { get; set; } + public int SupplierId { get; set; } + public string SupplierName { get; set; } = string.Empty; + public PurchaseOrderStatus Status { get; set; } + public int? DestinationWarehouseId { get; set; } + public string? DestinationWarehouseName { get; set; } + public string? Notes { get; set; } + public decimal TotalNet { get; set; } + public decimal TotalTax { get; set; } + public decimal TotalGross { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public List Lines { get; set; } = new(); +} + +public class PurchaseOrderLineDto +{ + public int Id { get; set; } + public int PurchaseOrderId { get; set; } + public int WarehouseArticleId { get; set; } + public string ArticleCode { get; set; } = string.Empty; + public string ArticleDescription { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public decimal Quantity { get; set; } + public decimal ReceivedQuantity { get; set; } + public decimal UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public decimal DiscountPercent { get; set; } + public decimal LineTotal { get; set; } +} + +public class CreatePurchaseOrderDto +{ + public DateTime OrderDate { get; set; } = DateTime.Now; + public DateTime? ExpectedDeliveryDate { get; set; } + public int SupplierId { get; set; } + public int? DestinationWarehouseId { get; set; } + public string? Notes { get; set; } + public List Lines { get; set; } = new(); +} + +public class CreatePurchaseOrderLineDto +{ + public int WarehouseArticleId { get; set; } + public string? Description { get; set; } // Optional override + public decimal Quantity { get; set; } + public decimal UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public decimal DiscountPercent { get; set; } +} + +public class UpdatePurchaseOrderDto +{ + public DateTime OrderDate { get; set; } + public DateTime? ExpectedDeliveryDate { get; set; } + public int? DestinationWarehouseId { get; set; } + public string? Notes { get; set; } + public List Lines { get; set; } = new(); +} + +public class UpdatePurchaseOrderLineDto +{ + public int? Id { get; set; } // Null if new line + public int WarehouseArticleId { get; set; } + public string? Description { get; set; } + public decimal Quantity { get; set; } + public decimal UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public decimal DiscountPercent { get; set; } + public bool IsDeleted { get; set; } // To mark for deletion +} diff --git a/src/Apollinare.API/Modules/Purchases/Dtos/SupplierDtos.cs b/src/Apollinare.API/Modules/Purchases/Dtos/SupplierDtos.cs new file mode 100644 index 0000000..d5e697d --- /dev/null +++ b/src/Apollinare.API/Modules/Purchases/Dtos/SupplierDtos.cs @@ -0,0 +1,63 @@ +using System; + +namespace Apollinare.API.Modules.Purchases.Dtos; + +public class SupplierDto +{ + public int Id { get; set; } + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string? VatNumber { get; set; } + public string? FiscalCode { get; set; } + public string? Address { get; set; } + public string? City { get; set; } + public string? Province { get; set; } + public string? ZipCode { get; set; } + public string? Country { get; set; } + public string? Email { get; set; } + public string? Pec { get; set; } + public string? Phone { get; set; } + public string? Website { get; set; } + public string? PaymentTerms { get; set; } + public string? Notes { get; set; } + public bool IsActive { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } +} + +public class CreateSupplierDto +{ + public string Name { get; set; } = string.Empty; + public string? VatNumber { get; set; } + public string? FiscalCode { get; set; } + public string? Address { get; set; } + public string? City { get; set; } + public string? Province { get; set; } + public string? ZipCode { get; set; } + public string? Country { get; set; } = "Italia"; + public string? Email { get; set; } + public string? Pec { get; set; } + public string? Phone { get; set; } + public string? Website { get; set; } + public string? PaymentTerms { get; set; } + public string? Notes { get; set; } +} + +public class UpdateSupplierDto +{ + public string Name { get; set; } = string.Empty; + public string? VatNumber { get; set; } + public string? FiscalCode { get; set; } + public string? Address { get; set; } + public string? City { get; set; } + public string? Province { get; set; } + public string? ZipCode { get; set; } + public string? Country { get; set; } + public string? Email { get; set; } + public string? Pec { get; set; } + public string? Phone { get; set; } + public string? Website { get; set; } + public string? PaymentTerms { get; set; } + public string? Notes { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/Apollinare.API/Modules/Purchases/Services/PurchaseService.cs b/src/Apollinare.API/Modules/Purchases/Services/PurchaseService.cs new file mode 100644 index 0000000..a0a8342 --- /dev/null +++ b/src/Apollinare.API/Modules/Purchases/Services/PurchaseService.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Apollinare.API.Modules.Purchases.Dtos; +using Apollinare.API.Modules.Warehouse.Services; +using Apollinare.API.Services; +using Apollinare.Domain.Entities.Purchases; +using Apollinare.Domain.Entities.Warehouse; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Apollinare.API.Modules.Purchases.Services; + +public class PurchaseService +{ + private readonly AppollinareDbContext _db; + private readonly AutoCodeService _autoCodeService; + private readonly IWarehouseService _warehouseService; + + public PurchaseService(AppollinareDbContext db, AutoCodeService autoCodeService, IWarehouseService warehouseService) + { + _db = db; + _autoCodeService = autoCodeService; + _warehouseService = warehouseService; + } + + public async Task> GetAllAsync() + { + return await _db.PurchaseOrders + .AsNoTracking() + .Include(o => o.Supplier) + .Include(o => o.DestinationWarehouse) + .OrderByDescending(o => o.OrderDate) + .Select(o => new PurchaseOrderDto + { + Id = o.Id, + OrderNumber = o.OrderNumber, + OrderDate = o.OrderDate, + ExpectedDeliveryDate = o.ExpectedDeliveryDate, + SupplierId = o.SupplierId, + SupplierName = o.Supplier!.Name, + Status = o.Status, + DestinationWarehouseId = o.DestinationWarehouseId, + DestinationWarehouseName = o.DestinationWarehouse != null ? o.DestinationWarehouse.Name : null, + Notes = o.Notes, + TotalNet = o.TotalNet, + TotalTax = o.TotalTax, + TotalGross = o.TotalGross, + CreatedAt = o.CreatedAt, + UpdatedAt = o.UpdatedAt + }) + .ToListAsync(); + } + + public async Task GetByIdAsync(int id) + { + var order = await _db.PurchaseOrders + .AsNoTracking() + .Include(o => o.Supplier) + .Include(o => o.DestinationWarehouse) + .Include(o => o.Lines) + .ThenInclude(l => l.WarehouseArticle) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order == null) return null; + + return new PurchaseOrderDto + { + Id = order.Id, + OrderNumber = order.OrderNumber, + OrderDate = order.OrderDate, + ExpectedDeliveryDate = order.ExpectedDeliveryDate, + SupplierId = order.SupplierId, + SupplierName = order.Supplier!.Name, + Status = order.Status, + DestinationWarehouseId = order.DestinationWarehouseId, + DestinationWarehouseName = order.DestinationWarehouse?.Name, + Notes = order.Notes, + TotalNet = order.TotalNet, + TotalTax = order.TotalTax, + TotalGross = order.TotalGross, + CreatedAt = order.CreatedAt, + UpdatedAt = order.UpdatedAt, + Lines = order.Lines.Select(l => new PurchaseOrderLineDto + { + Id = l.Id, + PurchaseOrderId = l.PurchaseOrderId, + WarehouseArticleId = l.WarehouseArticleId, + ArticleCode = l.WarehouseArticle!.Code, + ArticleDescription = l.WarehouseArticle.Description, + Description = l.Description, + Quantity = l.Quantity, + ReceivedQuantity = l.ReceivedQuantity, + UnitPrice = l.UnitPrice, + TaxRate = l.TaxRate, + DiscountPercent = l.DiscountPercent, + LineTotal = l.LineTotal + }).ToList() + }; + } + + public async Task CreateAsync(CreatePurchaseOrderDto dto) + { + var code = await _autoCodeService.GenerateNextCodeAsync("purchase_order"); + if (string.IsNullOrEmpty(code)) + { + code = $"ODA{DateTime.Now:yyyy}-{Guid.NewGuid().ToString().Substring(0, 5).ToUpper()}"; + } + + var order = new PurchaseOrder + { + OrderNumber = code, + OrderDate = dto.OrderDate, + ExpectedDeliveryDate = dto.ExpectedDeliveryDate, + SupplierId = dto.SupplierId, + DestinationWarehouseId = dto.DestinationWarehouseId, + Notes = dto.Notes, + Status = PurchaseOrderStatus.Draft + }; + + foreach (var lineDto in dto.Lines) + { + var line = new PurchaseOrderLine + { + WarehouseArticleId = lineDto.WarehouseArticleId, + Description = lineDto.Description ?? string.Empty, + Quantity = lineDto.Quantity, + UnitPrice = lineDto.UnitPrice, + TaxRate = lineDto.TaxRate, + DiscountPercent = lineDto.DiscountPercent + }; + + // If description is empty, fetch from article + if (string.IsNullOrEmpty(line.Description)) + { + var article = await _db.WarehouseArticles.FindAsync(line.WarehouseArticleId); + if (article != null) line.Description = article.Description; + } + + // Calculate totals + var netPrice = line.UnitPrice * (1 - line.DiscountPercent / 100); + line.LineTotal = Math.Round(netPrice * line.Quantity, 2); + + order.Lines.Add(line); + } + + CalculateOrderTotals(order); + + _db.PurchaseOrders.Add(order); + await _db.SaveChangesAsync(); + + return await GetByIdAsync(order.Id) ?? throw new InvalidOperationException("Failed to retrieve created order"); + } + + public async Task UpdateAsync(int id, UpdatePurchaseOrderDto dto) + { + var order = await _db.PurchaseOrders + .Include(o => o.Lines) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order == null) return null; + if (order.Status != PurchaseOrderStatus.Draft) + throw new InvalidOperationException("Solo gli ordini in bozza possono essere modificati"); + + order.OrderDate = dto.OrderDate; + order.ExpectedDeliveryDate = dto.ExpectedDeliveryDate; + order.DestinationWarehouseId = dto.DestinationWarehouseId; + order.Notes = dto.Notes; + order.UpdatedAt = DateTime.Now; + + // Update lines + foreach (var lineDto in dto.Lines) + { + if (lineDto.IsDeleted) + { + if (lineDto.Id.HasValue) + { + var lineToDelete = order.Lines.FirstOrDefault(l => l.Id == lineDto.Id.Value); + if (lineToDelete != null) order.Lines.Remove(lineToDelete); + } + continue; + } + + PurchaseOrderLine line; + if (lineDto.Id.HasValue) + { + line = order.Lines.FirstOrDefault(l => l.Id == lineDto.Id.Value) + ?? throw new KeyNotFoundException($"Line {lineDto.Id} not found"); + } + else + { + line = new PurchaseOrderLine(); + order.Lines.Add(line); + } + + line.WarehouseArticleId = lineDto.WarehouseArticleId; + line.Description = lineDto.Description ?? string.Empty; + line.Quantity = lineDto.Quantity; + line.UnitPrice = lineDto.UnitPrice; + line.TaxRate = lineDto.TaxRate; + line.DiscountPercent = lineDto.DiscountPercent; + + if (string.IsNullOrEmpty(line.Description)) + { + var article = await _db.WarehouseArticles.FindAsync(line.WarehouseArticleId); + if (article != null) line.Description = article.Description; + } + + var netPrice = line.UnitPrice * (1 - line.DiscountPercent / 100); + line.LineTotal = Math.Round(netPrice * line.Quantity, 2); + } + + CalculateOrderTotals(order); + + await _db.SaveChangesAsync(); + return await GetByIdAsync(id); + } + + public async Task DeleteAsync(int id) + { + var order = await _db.PurchaseOrders.FindAsync(id); + if (order == null) return false; + if (order.Status != PurchaseOrderStatus.Draft && order.Status != PurchaseOrderStatus.Cancelled) + throw new InvalidOperationException("Solo gli ordini in bozza o annullati possono essere eliminati"); + + _db.PurchaseOrders.Remove(order); + await _db.SaveChangesAsync(); + return true; + } + + public async Task ConfirmOrderAsync(int id) + { + var order = await _db.PurchaseOrders.FindAsync(id); + if (order == null) throw new KeyNotFoundException("Order not found"); + if (order.Status != PurchaseOrderStatus.Draft) throw new InvalidOperationException("Solo gli ordini in bozza possono essere confermati"); + + order.Status = PurchaseOrderStatus.Confirmed; + order.UpdatedAt = DateTime.Now; + + await _db.SaveChangesAsync(); + return await GetByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve order"); + } + + public async Task ReceiveOrderAsync(int id) + { + var order = await _db.PurchaseOrders + .Include(o => o.Lines) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order == null) throw new KeyNotFoundException("Order not found"); + if (order.Status != PurchaseOrderStatus.Confirmed && order.Status != PurchaseOrderStatus.PartiallyReceived) + throw new InvalidOperationException("L'ordine deve essere confermato per essere ricevuto"); + + // Create Stock Movement (Inbound) + var warehouseId = order.DestinationWarehouseId; + if (!warehouseId.HasValue) + { + var defaultWarehouse = await _warehouseService.GetDefaultWarehouseAsync(); + warehouseId = defaultWarehouse?.Id; + } + + if (!warehouseId.HasValue) throw new InvalidOperationException("Nessun magazzino di destinazione specificato o di default"); + + // Genera numero documento movimento + var docNumber = await _warehouseService.GenerateDocumentNumberAsync(MovementType.Inbound); + + // Trova causale di default per acquisto (se esiste, altrimenti null o crea) + var reason = (await _warehouseService.GetMovementReasonsAsync(MovementType.Inbound)) + .FirstOrDefault(r => r.Code == "ACQ" || r.Description.Contains("Acquisto")); + + var movement = new StockMovement + { + DocumentNumber = docNumber, + MovementDate = DateTime.Now, + Type = MovementType.Inbound, + Status = MovementStatus.Draft, + DestinationWarehouseId = warehouseId, + ReasonId = reason?.Id, + ExternalReference = order.OrderNumber, + Notes = $"Ricevimento merce da ordine {order.OrderNumber}" + }; + + movement = await _warehouseService.CreateMovementAsync(movement); + + // Add lines to movement + foreach (var line in order.Lines) + { + var remainingQty = line.Quantity - line.ReceivedQuantity; + if (remainingQty <= 0) continue; + + // Update received quantity on order line + line.ReceivedQuantity += remainingQty; + + // Add movement line directly via DbContext since IWarehouseService doesn't expose AddLine (it exposes UpdateMovement) + // Or better, construct the movement with lines initially if possible. + // Since CreateMovementAsync saves, we need to add lines and save again. + + var movementLine = new StockMovementLine + { + MovementId = movement.Id, + ArticleId = line.WarehouseArticleId, + Quantity = remainingQty, + UnitCost = line.UnitPrice * (1 - line.DiscountPercent / 100), + LineValue = Math.Round(remainingQty * (line.UnitPrice * (1 - line.DiscountPercent / 100)), 2) + }; + + _db.StockMovementLines.Add(movementLine); + } + + await _db.SaveChangesAsync(); + + // Confirm movement to update stock + await _warehouseService.ConfirmMovementAsync(movement.Id); + + // Update order status + var allReceived = order.Lines.All(l => l.ReceivedQuantity >= l.Quantity); + order.Status = allReceived ? PurchaseOrderStatus.Received : PurchaseOrderStatus.PartiallyReceived; + order.UpdatedAt = DateTime.Now; + + await _db.SaveChangesAsync(); + return await GetByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve order"); + } + + private void CalculateOrderTotals(PurchaseOrder order) + { + order.TotalNet = 0; + order.TotalTax = 0; + order.TotalGross = 0; + + foreach (var line in order.Lines) + { + order.TotalNet += line.LineTotal; + var taxAmount = line.LineTotal * (line.TaxRate / 100); + order.TotalTax += taxAmount; + } + + order.TotalGross = order.TotalNet + order.TotalTax; + } +} diff --git a/src/Apollinare.API/Modules/Purchases/Services/SupplierService.cs b/src/Apollinare.API/Modules/Purchases/Services/SupplierService.cs new file mode 100644 index 0000000..4c381a4 --- /dev/null +++ b/src/Apollinare.API/Modules/Purchases/Services/SupplierService.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Apollinare.API.Modules.Purchases.Dtos; +using Apollinare.API.Services; +using Apollinare.Domain.Entities.Purchases; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Apollinare.API.Modules.Purchases.Services; + +public class SupplierService +{ + private readonly AppollinareDbContext _db; + private readonly AutoCodeService _autoCodeService; + + public SupplierService(AppollinareDbContext db, AutoCodeService autoCodeService) + { + _db = db; + _autoCodeService = autoCodeService; + } + + public async Task> GetAllAsync() + { + return await _db.Suppliers + .AsNoTracking() + .OrderBy(s => s.Name) + .Select(s => new SupplierDto + { + Id = s.Id, + Code = s.Code, + Name = s.Name, + VatNumber = s.VatNumber, + FiscalCode = s.FiscalCode, + Address = s.Address, + City = s.City, + Province = s.Province, + ZipCode = s.ZipCode, + Country = s.Country, + Email = s.Email, + Pec = s.Pec, + Phone = s.Phone, + Website = s.Website, + PaymentTerms = s.PaymentTerms, + Notes = s.Notes, + IsActive = s.IsActive, + CreatedAt = s.CreatedAt, + UpdatedAt = s.UpdatedAt + }) + .ToListAsync(); + } + + public async Task GetByIdAsync(int id) + { + var supplier = await _db.Suppliers + .AsNoTracking() + .FirstOrDefaultAsync(s => s.Id == id); + + if (supplier == null) return null; + + return new SupplierDto + { + Id = supplier.Id, + Code = supplier.Code, + Name = supplier.Name, + VatNumber = supplier.VatNumber, + FiscalCode = supplier.FiscalCode, + Address = supplier.Address, + City = supplier.City, + Province = supplier.Province, + ZipCode = supplier.ZipCode, + Country = supplier.Country, + Email = supplier.Email, + Pec = supplier.Pec, + Phone = supplier.Phone, + Website = supplier.Website, + PaymentTerms = supplier.PaymentTerms, + Notes = supplier.Notes, + IsActive = supplier.IsActive, + CreatedAt = supplier.CreatedAt, + UpdatedAt = supplier.UpdatedAt + }; + } + + public async Task CreateAsync(CreateSupplierDto dto) + { + // Genera codice automatico + var code = await _autoCodeService.GenerateNextCodeAsync("supplier"); + if (string.IsNullOrEmpty(code)) + { + // Fallback se disabilitato + code = $"FOR-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString().Substring(0, 4).ToUpper()}"; + } + + var supplier = new Supplier + { + Code = code, + Name = dto.Name, + VatNumber = dto.VatNumber, + FiscalCode = dto.FiscalCode, + Address = dto.Address, + City = dto.City, + Province = dto.Province, + ZipCode = dto.ZipCode, + Country = dto.Country, + Email = dto.Email, + Pec = dto.Pec, + Phone = dto.Phone, + Website = dto.Website, + PaymentTerms = dto.PaymentTerms, + Notes = dto.Notes, + IsActive = true, + CreatedAt = DateTime.Now + }; + + _db.Suppliers.Add(supplier); + await _db.SaveChangesAsync(); + + return await GetByIdAsync(supplier.Id) ?? throw new InvalidOperationException("Failed to retrieve created supplier"); + } + + public async Task UpdateAsync(int id, UpdateSupplierDto dto) + { + var supplier = await _db.Suppliers.FindAsync(id); + if (supplier == null) return null; + + supplier.Name = dto.Name; + supplier.VatNumber = dto.VatNumber; + supplier.FiscalCode = dto.FiscalCode; + supplier.Address = dto.Address; + supplier.City = dto.City; + supplier.Province = dto.Province; + supplier.ZipCode = dto.ZipCode; + supplier.Country = dto.Country; + supplier.Email = dto.Email; + supplier.Pec = dto.Pec; + supplier.Phone = dto.Phone; + supplier.Website = dto.Website; + supplier.PaymentTerms = dto.PaymentTerms; + supplier.Notes = dto.Notes; + supplier.IsActive = dto.IsActive; + supplier.UpdatedAt = DateTime.Now; + + await _db.SaveChangesAsync(); + + return await GetByIdAsync(id); + } + + public async Task DeleteAsync(int id) + { + var supplier = await _db.Suppliers.FindAsync(id); + if (supplier == null) return false; + + // Check if used in purchase orders + var hasOrders = await _db.PurchaseOrders.AnyAsync(o => o.SupplierId == id); + if (hasOrders) + { + throw new InvalidOperationException("Impossibile eliminare il fornitore perché ha ordini associati."); + } + + _db.Suppliers.Remove(supplier); + await _db.SaveChangesAsync(); + return true; + } +} diff --git a/src/Apollinare.API/Modules/Sales/Controllers/SalesOrdersController.cs b/src/Apollinare.API/Modules/Sales/Controllers/SalesOrdersController.cs new file mode 100644 index 0000000..f655d01 --- /dev/null +++ b/src/Apollinare.API/Modules/Sales/Controllers/SalesOrdersController.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Apollinare.API.Modules.Sales.Dtos; +using Apollinare.API.Modules.Sales.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Modules.Sales.Controllers; + +[ApiController] +[Route("api/sales/orders")] +public class SalesOrdersController : ControllerBase +{ + private readonly SalesService _service; + + public SalesOrdersController(SalesService service) + { + _service = service; + } + + [HttpGet] + public async Task>> GetAll() + { + return Ok(await _service.GetAllAsync()); + } + + [HttpGet("{id}")] + public async Task> GetById(int id) + { + var order = await _service.GetByIdAsync(id); + if (order == null) return NotFound(); + return Ok(order); + } + + [HttpPost] + public async Task> Create(CreateSalesOrderDto dto) + { + var order = await _service.CreateAsync(dto); + return CreatedAtAction(nameof(GetById), new { id = order.Id }, order); + } + + [HttpPut("{id}")] + public async Task> Update(int id, UpdateSalesOrderDto dto) + { + try + { + var order = await _service.UpdateAsync(id, dto); + if (order == null) return NotFound(); + return Ok(order); + } + catch (System.InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("{id}")] + public async Task Delete(int id) + { + try + { + var result = await _service.DeleteAsync(id); + if (!result) return NotFound(); + return NoContent(); + } + catch (System.InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("{id}/confirm")] + public async Task> Confirm(int id) + { + try + { + var order = await _service.ConfirmOrderAsync(id); + return Ok(order); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (System.InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("{id}/ship")] + public async Task> Ship(int id) + { + try + { + var order = await _service.ShipOrderAsync(id); + return Ok(order); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (System.InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } +} diff --git a/src/Apollinare.API/Modules/Sales/Dtos/SalesOrderDtos.cs b/src/Apollinare.API/Modules/Sales/Dtos/SalesOrderDtos.cs new file mode 100644 index 0000000..da015dc --- /dev/null +++ b/src/Apollinare.API/Modules/Sales/Dtos/SalesOrderDtos.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using Apollinare.Domain.Entities.Sales; + +namespace Apollinare.API.Modules.Sales.Dtos; + +public class SalesOrderDto +{ + public int Id { get; set; } + public string OrderNumber { get; set; } = string.Empty; + public DateTime OrderDate { get; set; } + public DateTime? ExpectedDeliveryDate { get; set; } + public int CustomerId { get; set; } + public string CustomerName { get; set; } = string.Empty; + public SalesOrderStatus Status { get; set; } + public string? Notes { get; set; } + public decimal TotalNet { get; set; } + public decimal TotalTax { get; set; } + public decimal TotalGross { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public List Lines { get; set; } = new(); +} + +public class SalesOrderLineDto +{ + public int Id { get; set; } + public int SalesOrderId { get; set; } + public int WarehouseArticleId { get; set; } + public string ArticleCode { get; set; } = string.Empty; + public string ArticleDescription { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public decimal Quantity { get; set; } + public decimal ShippedQuantity { get; set; } + public decimal UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public decimal DiscountPercent { get; set; } + public decimal LineTotal { get; set; } +} + +public class CreateSalesOrderDto +{ + public DateTime OrderDate { get; set; } = DateTime.Now; + public DateTime? ExpectedDeliveryDate { get; set; } + public int CustomerId { get; set; } + public string? Notes { get; set; } + public List Lines { get; set; } = new(); +} + +public class CreateSalesOrderLineDto +{ + public int WarehouseArticleId { get; set; } + public string? Description { get; set; } // Optional override + public decimal Quantity { get; set; } + public decimal UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public decimal DiscountPercent { get; set; } +} + +public class UpdateSalesOrderDto +{ + public DateTime OrderDate { get; set; } + public DateTime? ExpectedDeliveryDate { get; set; } + public string? Notes { get; set; } + public List Lines { get; set; } = new(); +} + +public class UpdateSalesOrderLineDto +{ + public int? Id { get; set; } // Null if new line + public int WarehouseArticleId { get; set; } + public string? Description { get; set; } + public decimal Quantity { get; set; } + public decimal UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public decimal DiscountPercent { get; set; } + public bool IsDeleted { get; set; } // To mark for deletion +} diff --git a/src/Apollinare.API/Modules/Sales/Services/SalesService.cs b/src/Apollinare.API/Modules/Sales/Services/SalesService.cs new file mode 100644 index 0000000..9038aec --- /dev/null +++ b/src/Apollinare.API/Modules/Sales/Services/SalesService.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Apollinare.API.Modules.Sales.Dtos; +using Apollinare.API.Modules.Warehouse.Services; +using Apollinare.API.Services; +using Apollinare.Domain.Entities.Sales; +using Apollinare.Domain.Entities.Warehouse; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Apollinare.API.Modules.Sales.Services; + +public class SalesService +{ + private readonly AppollinareDbContext _db; + private readonly AutoCodeService _autoCodeService; + private readonly IWarehouseService _warehouseService; + + public SalesService(AppollinareDbContext db, AutoCodeService autoCodeService, IWarehouseService warehouseService) + { + _db = db; + _autoCodeService = autoCodeService; + _warehouseService = warehouseService; + } + + public async Task> GetAllAsync() + { + return await _db.SalesOrders + .AsNoTracking() + .Include(o => o.Customer) + .OrderByDescending(o => o.OrderDate) + .Select(o => new SalesOrderDto + { + Id = o.Id, + OrderNumber = o.OrderNumber, + OrderDate = o.OrderDate, + ExpectedDeliveryDate = o.ExpectedDeliveryDate, + CustomerId = o.CustomerId, + CustomerName = o.Customer!.RagioneSociale, + Status = o.Status, + Notes = o.Notes, + TotalNet = o.TotalNet, + TotalTax = o.TotalTax, + TotalGross = o.TotalGross, + CreatedAt = o.CreatedAt, + UpdatedAt = o.UpdatedAt + }) + .ToListAsync(); + } + + public async Task GetByIdAsync(int id) + { + var order = await _db.SalesOrders + .AsNoTracking() + .Include(o => o.Customer) + .Include(o => o.Lines) + .ThenInclude(l => l.WarehouseArticle) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order == null) return null; + + return new SalesOrderDto + { + Id = order.Id, + OrderNumber = order.OrderNumber, + OrderDate = order.OrderDate, + ExpectedDeliveryDate = order.ExpectedDeliveryDate, + CustomerId = order.CustomerId, + CustomerName = order.Customer!.RagioneSociale, + Status = order.Status, + Notes = order.Notes, + TotalNet = order.TotalNet, + TotalTax = order.TotalTax, + TotalGross = order.TotalGross, + CreatedAt = order.CreatedAt, + UpdatedAt = order.UpdatedAt, + Lines = order.Lines.Select(l => new SalesOrderLineDto + { + Id = l.Id, + SalesOrderId = l.SalesOrderId, + WarehouseArticleId = l.WarehouseArticleId, + ArticleCode = l.WarehouseArticle!.Code, + ArticleDescription = l.WarehouseArticle.Description, + Description = l.Description, + Quantity = l.Quantity, + ShippedQuantity = l.ShippedQuantity, + UnitPrice = l.UnitPrice, + TaxRate = l.TaxRate, + DiscountPercent = l.DiscountPercent, + LineTotal = l.LineTotal + }).ToList() + }; + } + + public async Task CreateAsync(CreateSalesOrderDto dto) + { + var code = await _autoCodeService.GenerateNextCodeAsync("sales_order"); + if (string.IsNullOrEmpty(code)) + { + code = $"ODV{DateTime.Now:yyyy}-{Guid.NewGuid().ToString().Substring(0, 5).ToUpper()}"; + } + + var order = new SalesOrder + { + OrderNumber = code, + OrderDate = dto.OrderDate, + ExpectedDeliveryDate = dto.ExpectedDeliveryDate, + CustomerId = dto.CustomerId, + Notes = dto.Notes, + Status = SalesOrderStatus.Draft + }; + + foreach (var lineDto in dto.Lines) + { + var line = new SalesOrderLine + { + WarehouseArticleId = lineDto.WarehouseArticleId, + Description = lineDto.Description ?? string.Empty, + Quantity = lineDto.Quantity, + UnitPrice = lineDto.UnitPrice, + TaxRate = lineDto.TaxRate, + DiscountPercent = lineDto.DiscountPercent + }; + + // If description is empty, fetch from article + if (string.IsNullOrEmpty(line.Description)) + { + var article = await _db.WarehouseArticles.FindAsync(line.WarehouseArticleId); + if (article != null) line.Description = article.Description; + } + + // Calculate totals + var netPrice = line.UnitPrice * (1 - line.DiscountPercent / 100); + line.LineTotal = Math.Round(netPrice * line.Quantity, 2); + + order.Lines.Add(line); + } + + CalculateOrderTotals(order); + + _db.SalesOrders.Add(order); + await _db.SaveChangesAsync(); + + return await GetByIdAsync(order.Id) ?? throw new InvalidOperationException("Failed to retrieve created order"); + } + + public async Task UpdateAsync(int id, UpdateSalesOrderDto dto) + { + var order = await _db.SalesOrders + .Include(o => o.Lines) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order == null) return null; + if (order.Status != SalesOrderStatus.Draft) + throw new InvalidOperationException("Solo gli ordini in bozza possono essere modificati"); + + order.OrderDate = dto.OrderDate; + order.ExpectedDeliveryDate = dto.ExpectedDeliveryDate; + order.Notes = dto.Notes; + order.UpdatedAt = DateTime.Now; + + // Update lines + foreach (var lineDto in dto.Lines) + { + if (lineDto.IsDeleted) + { + if (lineDto.Id.HasValue) + { + var lineToDelete = order.Lines.FirstOrDefault(l => l.Id == lineDto.Id.Value); + if (lineToDelete != null) order.Lines.Remove(lineToDelete); + } + continue; + } + + SalesOrderLine line; + if (lineDto.Id.HasValue) + { + line = order.Lines.FirstOrDefault(l => l.Id == lineDto.Id.Value) + ?? throw new KeyNotFoundException($"Line {lineDto.Id} not found"); + } + else + { + line = new SalesOrderLine(); + order.Lines.Add(line); + } + + line.WarehouseArticleId = lineDto.WarehouseArticleId; + line.Description = lineDto.Description ?? string.Empty; + line.Quantity = lineDto.Quantity; + line.UnitPrice = lineDto.UnitPrice; + line.TaxRate = lineDto.TaxRate; + line.DiscountPercent = lineDto.DiscountPercent; + + if (string.IsNullOrEmpty(line.Description)) + { + var article = await _db.WarehouseArticles.FindAsync(line.WarehouseArticleId); + if (article != null) line.Description = article.Description; + } + + var netPrice = line.UnitPrice * (1 - line.DiscountPercent / 100); + line.LineTotal = Math.Round(netPrice * line.Quantity, 2); + } + + CalculateOrderTotals(order); + + await _db.SaveChangesAsync(); + return await GetByIdAsync(id); + } + + public async Task DeleteAsync(int id) + { + var order = await _db.SalesOrders.FindAsync(id); + if (order == null) return false; + if (order.Status != SalesOrderStatus.Draft && order.Status != SalesOrderStatus.Cancelled) + throw new InvalidOperationException("Solo gli ordini in bozza o annullati possono essere eliminati"); + + _db.SalesOrders.Remove(order); + await _db.SaveChangesAsync(); + return true; + } + + public async Task ConfirmOrderAsync(int id) + { + var order = await _db.SalesOrders.FindAsync(id); + if (order == null) throw new KeyNotFoundException("Order not found"); + if (order.Status != SalesOrderStatus.Draft) throw new InvalidOperationException("Solo gli ordini in bozza possono essere confermati"); + + order.Status = SalesOrderStatus.Confirmed; + order.UpdatedAt = DateTime.Now; + + await _db.SaveChangesAsync(); + return await GetByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve order"); + } + + public async Task ShipOrderAsync(int id) + { + var order = await _db.SalesOrders + .Include(o => o.Lines) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order == null) throw new KeyNotFoundException("Order not found"); + if (order.Status != SalesOrderStatus.Confirmed && order.Status != SalesOrderStatus.PartiallyShipped) + throw new InvalidOperationException("L'ordine deve essere confermato per essere spedito"); + + // Create Stock Movement (Outbound) + var defaultWarehouse = await _warehouseService.GetDefaultWarehouseAsync(); + var warehouseId = defaultWarehouse?.Id; + + if (!warehouseId.HasValue) throw new InvalidOperationException("Nessun magazzino di default trovato per la spedizione"); + + // Genera numero documento movimento + var docNumber = await _warehouseService.GenerateDocumentNumberAsync(MovementType.Outbound); + + // Trova causale di default per vendita + var reason = (await _warehouseService.GetMovementReasonsAsync(MovementType.Outbound)) + .FirstOrDefault(r => r.Code == "VEN" || r.Description.Contains("Vendita")); + + var movement = new StockMovement + { + DocumentNumber = docNumber, + MovementDate = DateTime.Now, + Type = MovementType.Outbound, + Status = MovementStatus.Draft, + SourceWarehouseId = warehouseId, + ReasonId = reason?.Id, + ExternalReference = order.OrderNumber, + Notes = $"Spedizione merce ordine {order.OrderNumber}" + }; + + movement = await _warehouseService.CreateMovementAsync(movement); + + // Add lines to movement + foreach (var line in order.Lines) + { + var remainingQty = line.Quantity - line.ShippedQuantity; + if (remainingQty <= 0) continue; + + // Update shipped quantity on order line + line.ShippedQuantity += remainingQty; + + var movementLine = new StockMovementLine + { + MovementId = movement.Id, + ArticleId = line.WarehouseArticleId, + Quantity = remainingQty, + UnitCost = 0, // Outbound movement cost is calculated by FIFO/LIFO/Avg logic usually, but here we just set 0 or let the system handle it during confirmation + LineValue = 0 + }; + + _db.StockMovementLines.Add(movementLine); + } + + await _db.SaveChangesAsync(); + + // Confirm movement to update stock + await _warehouseService.ConfirmMovementAsync(movement.Id); + + // Update order status + var allShipped = order.Lines.All(l => l.ShippedQuantity >= l.Quantity); + order.Status = allShipped ? SalesOrderStatus.Shipped : SalesOrderStatus.PartiallyShipped; + order.UpdatedAt = DateTime.Now; + + await _db.SaveChangesAsync(); + return await GetByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve order"); + } + + private void CalculateOrderTotals(SalesOrder order) + { + order.TotalNet = 0; + order.TotalTax = 0; + order.TotalGross = 0; + + foreach (var line in order.Lines) + { + order.TotalNet += line.LineTotal; + var taxAmount = line.LineTotal * (line.TaxRate / 100); + order.TotalTax += taxAmount; + } + + order.TotalGross = order.TotalNet + order.TotalTax; + } +} diff --git a/src/Apollinare.API/Program.cs b/src/Apollinare.API/Program.cs index 7fa626d..111e32c 100644 --- a/src/Apollinare.API/Program.cs +++ b/src/Apollinare.API/Program.cs @@ -3,6 +3,9 @@ using Apollinare.API.Services; // Trigger rebuild using Apollinare.API.Services.Reports; using Apollinare.API.Modules.Warehouse.Services; +using Apollinare.API.Modules.Purchases.Services; +using Apollinare.API.Modules.Sales.Services; +using Apollinare.API.Modules.Production.Services; using Apollinare.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using System.Text.Json.Serialization; @@ -27,6 +30,17 @@ builder.Services.AddSingleton(); // Warehouse Module Services builder.Services.AddScoped(); +// Purchases Module Services +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// Sales Module Services +builder.Services.AddScoped(); + +// Production Module Services +builder.Services.AddScoped(); +builder.Services.AddScoped(); + // Memory cache for module state builder.Services.AddMemoryCache(); diff --git a/src/Apollinare.API/Services/AutoCodeService.cs b/src/Apollinare.API/Services/AutoCodeService.cs index b9684ac..54e40f3 100644 --- a/src/Apollinare.API/Services/AutoCodeService.cs +++ b/src/Apollinare.API/Services/AutoCodeService.cs @@ -136,6 +136,12 @@ public class AutoCodeService "articolo" => !await _db.Articoli .AnyAsync(a => a.Codice == code && (excludeId == null || a.Id != excludeId)), + "supplier" => !await _db.Suppliers + .AnyAsync(s => s.Code == code && (excludeId == null || s.Id != excludeId)), + + "purchase_order" => !await _db.PurchaseOrders + .AnyAsync(o => o.OrderNumber == code && (excludeId == null || o.Id != excludeId)), + _ => true // Entità non gestita, assume codice unico }; } diff --git a/src/Apollinare.API/apollinare.db-shm b/src/Apollinare.API/apollinare.db-shm deleted file mode 100644 index fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuAr62r3 Eventi { get; set; } = new List(); + public ICollection SalesOrders { get; set; } = new List(); } diff --git a/src/Apollinare.Domain/Entities/Production/BillOfMaterials.cs b/src/Apollinare.Domain/Entities/Production/BillOfMaterials.cs new file mode 100644 index 0000000..0382de1 --- /dev/null +++ b/src/Apollinare.Domain/Entities/Production/BillOfMaterials.cs @@ -0,0 +1,20 @@ +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Production; + +public class BillOfMaterials : BaseEntity +{ + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + + // The article that is produced + public int ArticleId { get; set; } + public WarehouseArticle Article { get; set; } = null!; + + // Quantity produced by this BOM (usually 1) + public decimal Quantity { get; set; } = 1; + + public bool IsActive { get; set; } = true; + + public ICollection Components { get; set; } = new List(); +} diff --git a/src/Apollinare.Domain/Entities/Production/BillOfMaterialsComponent.cs b/src/Apollinare.Domain/Entities/Production/BillOfMaterialsComponent.cs new file mode 100644 index 0000000..653fba4 --- /dev/null +++ b/src/Apollinare.Domain/Entities/Production/BillOfMaterialsComponent.cs @@ -0,0 +1,18 @@ +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Production; + +public class BillOfMaterialsComponent : BaseEntity +{ + public int BillOfMaterialsId { get; set; } + public BillOfMaterials BillOfMaterials { get; set; } = null!; + + // The raw material + public int ComponentArticleId { get; set; } + public WarehouseArticle ComponentArticle { get; set; } = null!; + + public decimal Quantity { get; set; } + + // Scrap percentage + public decimal ScrapPercentage { get; set; } +} diff --git a/src/Apollinare.Domain/Entities/Production/MrpSuggestion.cs b/src/Apollinare.Domain/Entities/Production/MrpSuggestion.cs new file mode 100644 index 0000000..a008214 --- /dev/null +++ b/src/Apollinare.Domain/Entities/Production/MrpSuggestion.cs @@ -0,0 +1,26 @@ +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Production; + +public class MrpSuggestion : BaseEntity +{ + public DateTime CalculationDate { get; set; } + + public int ArticleId { get; set; } + public WarehouseArticle Article { get; set; } = null!; + + public MrpSuggestionType Type { get; set; } + + public decimal Quantity { get; set; } + public DateTime SuggestionDate { get; set; } + + public string Reason { get; set; } = string.Empty; + + public bool IsProcessed { get; set; } +} + +public enum MrpSuggestionType +{ + Production = 0, + Purchase = 1 +} diff --git a/src/Apollinare.Domain/Entities/Production/ProductionCycle.cs b/src/Apollinare.Domain/Entities/Production/ProductionCycle.cs new file mode 100644 index 0000000..48d255e --- /dev/null +++ b/src/Apollinare.Domain/Entities/Production/ProductionCycle.cs @@ -0,0 +1,40 @@ +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Production; + +public class ProductionCycle : BaseEntity +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + + public int ArticleId { get; set; } + public WarehouseArticle Article { get; set; } = null!; + + public bool IsDefault { get; set; } + public bool IsActive { get; set; } = true; + + public ICollection Phases { get; set; } = new List(); +} + +public class ProductionCyclePhase : BaseEntity +{ + public int ProductionCycleId { get; set; } + public ProductionCycle ProductionCycle { get; set; } = null!; + + public int Sequence { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + + public int WorkCenterId { get; set; } + public WorkCenter WorkCenter { get; set; } = null!; + + /// + /// Duration in minutes per unit produced + /// + public int DurationPerUnitMinutes { get; set; } + + /// + /// Fixed setup time in minutes + /// + public int SetupTimeMinutes { get; set; } +} diff --git a/src/Apollinare.Domain/Entities/Production/ProductionOrder.cs b/src/Apollinare.Domain/Entities/Production/ProductionOrder.cs new file mode 100644 index 0000000..505011a --- /dev/null +++ b/src/Apollinare.Domain/Entities/Production/ProductionOrder.cs @@ -0,0 +1,39 @@ +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Production; + +public class ProductionOrder : BaseEntity +{ + public string Code { get; set; } = string.Empty; // Auto-generated + + public int ArticleId { get; set; } + public WarehouseArticle Article { get; set; } = null!; + + public decimal Quantity { get; set; } + + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + public DateTime DueDate { get; set; } + + public ProductionOrderStatus Status { get; set; } = ProductionOrderStatus.Draft; + + public string? Notes { get; set; } + + public ICollection Components { get; set; } = new List(); + public ICollection Phases { get; set; } = new List(); + + // Hierarchy + public int? ParentProductionOrderId { get; set; } + public ProductionOrder? ParentProductionOrder { get; set; } + public ICollection ChildProductionOrders { get; set; } = new List(); +} + +public enum ProductionOrderStatus +{ + Draft = 0, + Planned = 1, + Released = 2, + InProgress = 3, + Completed = 4, + Cancelled = 5 +} diff --git a/src/Apollinare.Domain/Entities/Production/ProductionOrderComponent.cs b/src/Apollinare.Domain/Entities/Production/ProductionOrderComponent.cs new file mode 100644 index 0000000..76007b4 --- /dev/null +++ b/src/Apollinare.Domain/Entities/Production/ProductionOrderComponent.cs @@ -0,0 +1,15 @@ +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Production; + +public class ProductionOrderComponent : BaseEntity +{ + public int ProductionOrderId { get; set; } + public ProductionOrder ProductionOrder { get; set; } = null!; + + public int ArticleId { get; set; } + public WarehouseArticle Article { get; set; } = null!; + + public decimal RequiredQuantity { get; set; } + public decimal ConsumedQuantity { get; set; } +} diff --git a/src/Apollinare.Domain/Entities/Production/ProductionOrderPhase.cs b/src/Apollinare.Domain/Entities/Production/ProductionOrderPhase.cs new file mode 100644 index 0000000..6982961 --- /dev/null +++ b/src/Apollinare.Domain/Entities/Production/ProductionOrderPhase.cs @@ -0,0 +1,34 @@ +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Production; + +public class ProductionOrderPhase : BaseEntity +{ + public int ProductionOrderId { get; set; } + public ProductionOrder ProductionOrder { get; set; } = null!; + + public int Sequence { get; set; } + public string Name { get; set; } = string.Empty; + + public int WorkCenterId { get; set; } + public WorkCenter WorkCenter { get; set; } = null!; + + public ProductionPhaseStatus Status { get; set; } = ProductionPhaseStatus.Pending; + + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + + public decimal QuantityCompleted { get; set; } + public decimal QuantityScrapped { get; set; } + + public int EstimatedDurationMinutes { get; set; } + public int ActualDurationMinutes { get; set; } +} + +public enum ProductionPhaseStatus +{ + Pending = 0, + InProgress = 1, + Completed = 2, + Paused = 3 +} diff --git a/src/Apollinare.Domain/Entities/Production/WorkCenter.cs b/src/Apollinare.Domain/Entities/Production/WorkCenter.cs new file mode 100644 index 0000000..59c71e7 --- /dev/null +++ b/src/Apollinare.Domain/Entities/Production/WorkCenter.cs @@ -0,0 +1,12 @@ +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Production; + +public class WorkCenter : BaseEntity +{ + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public decimal CostPerHour { get; set; } + public bool IsActive { get; set; } = true; +} diff --git a/src/Apollinare.Domain/Entities/Purchases/PurchaseOrder.cs b/src/Apollinare.Domain/Entities/Purchases/PurchaseOrder.cs new file mode 100644 index 0000000..0f817ec --- /dev/null +++ b/src/Apollinare.Domain/Entities/Purchases/PurchaseOrder.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using Apollinare.Domain.Entities; +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Purchases; + +/// +/// Ordine di acquisto a fornitore +/// +public class PurchaseOrder : BaseEntity +{ + /// + /// Numero ordine (generato automaticamente) + /// + public string OrderNumber { get; set; } = string.Empty; + + /// + /// Data ordine + /// + public DateTime OrderDate { get; set; } = DateTime.Now; + + /// + /// Data consegna prevista + /// + public DateTime? ExpectedDeliveryDate { get; set; } + + /// + /// ID Fornitore + /// + public int SupplierId { get; set; } + + /// + /// Stato dell'ordine + /// + public PurchaseOrderStatus Status { get; set; } = PurchaseOrderStatus.Draft; + + /// + /// ID Magazzino di destinazione (opzionale, se null usa il default) + /// + public int? DestinationWarehouseId { get; set; } + + /// + /// Note interne + /// + public string? Notes { get; set; } + + /// + /// Totale imponibile (calcolato) + /// + public decimal TotalNet { get; set; } + + /// + /// Totale tasse (calcolato) + /// + public decimal TotalTax { get; set; } + + /// + /// Totale lordo (calcolato) + /// + public decimal TotalGross { get; set; } + + // Navigation properties + public Supplier? Supplier { get; set; } + public WarehouseLocation? DestinationWarehouse { get; set; } + public ICollection Lines { get; set; } = new List(); +} + +public enum PurchaseOrderStatus +{ + /// + /// Bozza + /// + Draft = 0, + + /// + /// Confermato/Inviato al fornitore + /// + Confirmed = 1, + + /// + /// Ricevuto parzialmente + /// + PartiallyReceived = 2, + + /// + /// Ricevuto completamente + /// + Received = 3, + + /// + /// Annullato + /// + Cancelled = 4 +} diff --git a/src/Apollinare.Domain/Entities/Purchases/PurchaseOrderLine.cs b/src/Apollinare.Domain/Entities/Purchases/PurchaseOrderLine.cs new file mode 100644 index 0000000..50c5b2b --- /dev/null +++ b/src/Apollinare.Domain/Entities/Purchases/PurchaseOrderLine.cs @@ -0,0 +1,59 @@ +using Apollinare.Domain.Entities; +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Purchases; + +/// +/// Riga ordine di acquisto +/// +public class PurchaseOrderLine : BaseEntity +{ + /// + /// ID Ordine di acquisto + /// + public int PurchaseOrderId { get; set; } + + /// + /// ID Articolo di magazzino + /// + public int WarehouseArticleId { get; set; } + + /// + /// Descrizione (default da articolo, ma modificabile) + /// + public string Description { get; set; } = string.Empty; + + /// + /// Quantità ordinata + /// + public decimal Quantity { get; set; } + + /// + /// Quantità ricevuta + /// + public decimal ReceivedQuantity { get; set; } + + /// + /// Prezzo unitario + /// + public decimal UnitPrice { get; set; } + + /// + /// Aliquota IVA (percentuale) + /// + public decimal TaxRate { get; set; } + + /// + /// Sconto (percentuale) + /// + public decimal DiscountPercent { get; set; } + + /// + /// Totale riga (netto) + /// + public decimal LineTotal { get; set; } + + // Navigation properties + public PurchaseOrder? PurchaseOrder { get; set; } + public WarehouseArticle? WarehouseArticle { get; set; } +} diff --git a/src/Apollinare.Domain/Entities/Purchases/Supplier.cs b/src/Apollinare.Domain/Entities/Purchases/Supplier.cs new file mode 100644 index 0000000..7c2c2ab --- /dev/null +++ b/src/Apollinare.Domain/Entities/Purchases/Supplier.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using Apollinare.Domain.Entities; + +namespace Apollinare.Domain.Entities.Purchases; + +/// +/// Fornitore di beni o servizi +/// +public class Supplier : BaseEntity +{ + /// + /// Codice fornitore (generato automaticamente o manuale) + /// + public string Code { get; set; } = string.Empty; + + /// + /// Ragione sociale o nome + /// + public string Name { get; set; } = string.Empty; + + /// + /// Partita IVA + /// + public string? VatNumber { get; set; } + + /// + /// Codice Fiscale + /// + public string? FiscalCode { get; set; } + + /// + /// Indirizzo + /// + public string? Address { get; set; } + + /// + /// Città + /// + public string? City { get; set; } + + /// + /// Provincia + /// + public string? Province { get; set; } + + /// + /// CAP + /// + public string? ZipCode { get; set; } + + /// + /// Nazione + /// + public string? Country { get; set; } = "Italia"; + + /// + /// Email principale + /// + public string? Email { get; set; } + + /// + /// PEC + /// + public string? Pec { get; set; } + + /// + /// Telefono + /// + public string? Phone { get; set; } + + /// + /// Sito web + /// + public string? Website { get; set; } + + /// + /// Termini di pagamento (descrizione testuale o riferimento a tabella pagamenti) + /// + public string? PaymentTerms { get; set; } + + /// + /// Note interne + /// + public string? Notes { get; set; } + + /// + /// Se attivo, il fornitore può essere utilizzato + /// + public bool IsActive { get; set; } = true; + + // Navigation properties + public ICollection PurchaseOrders { get; set; } = new List(); +} diff --git a/src/Apollinare.Domain/Entities/Sales/SalesOrder.cs b/src/Apollinare.Domain/Entities/Sales/SalesOrder.cs new file mode 100644 index 0000000..21c0680 --- /dev/null +++ b/src/Apollinare.Domain/Entities/Sales/SalesOrder.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using Apollinare.Domain.Entities; +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Sales; + +/// +/// Ordine di vendita a cliente +/// +public class SalesOrder : BaseEntity +{ + /// + /// Numero ordine (generato automaticamente) + /// + public string OrderNumber { get; set; } = string.Empty; + + /// + /// Data ordine + /// + public DateTime OrderDate { get; set; } = DateTime.Now; + + /// + /// Data consegna prevista + /// + public DateTime? ExpectedDeliveryDate { get; set; } + + /// + /// ID Cliente + /// + public int CustomerId { get; set; } + + /// + /// Stato dell'ordine + /// + public SalesOrderStatus Status { get; set; } = SalesOrderStatus.Draft; + + /// + /// Note interne + /// + public string? Notes { get; set; } + + /// + /// Totale imponibile (calcolato) + /// + public decimal TotalNet { get; set; } + + /// + /// Totale tasse (calcolato) + /// + public decimal TotalTax { get; set; } + + /// + /// Totale lordo (calcolato) + /// + public decimal TotalGross { get; set; } + + // Navigation properties + public Cliente? Customer { get; set; } + public ICollection Lines { get; set; } = new List(); +} + +public enum SalesOrderStatus +{ + /// + /// Bozza + /// + Draft = 0, + + /// + /// Confermato + /// + Confirmed = 1, + + /// + /// Spedito parzialmente + /// + PartiallyShipped = 2, + + /// + /// Spedito completamente + /// + Shipped = 3, + + /// + /// Fatturato + /// + Invoiced = 4, + + /// + /// Annullato + /// + Cancelled = 5 +} diff --git a/src/Apollinare.Domain/Entities/Sales/SalesOrderLine.cs b/src/Apollinare.Domain/Entities/Sales/SalesOrderLine.cs new file mode 100644 index 0000000..ac050d1 --- /dev/null +++ b/src/Apollinare.Domain/Entities/Sales/SalesOrderLine.cs @@ -0,0 +1,59 @@ +using Apollinare.Domain.Entities; +using Apollinare.Domain.Entities.Warehouse; + +namespace Apollinare.Domain.Entities.Sales; + +/// +/// Riga ordine di vendita +/// +public class SalesOrderLine : BaseEntity +{ + /// + /// ID Ordine di vendita + /// + public int SalesOrderId { get; set; } + + /// + /// ID Articolo di magazzino + /// + public int WarehouseArticleId { get; set; } + + /// + /// Descrizione (default da articolo, ma modificabile) + /// + public string Description { get; set; } = string.Empty; + + /// + /// Quantità ordinata + /// + public decimal Quantity { get; set; } + + /// + /// Quantità spedita + /// + public decimal ShippedQuantity { get; set; } + + /// + /// Prezzo unitario + /// + public decimal UnitPrice { get; set; } + + /// + /// Aliquota IVA (percentuale) + /// + public decimal TaxRate { get; set; } + + /// + /// Sconto (percentuale) + /// + public decimal DiscountPercent { get; set; } + + /// + /// Totale riga (netto) + /// + public decimal LineTotal { get; set; } + + // Navigation properties + public SalesOrder? SalesOrder { get; set; } + public WarehouseArticle? WarehouseArticle { get; set; } +} diff --git a/src/Apollinare.Infrastructure/Data/AppollinareDbContext.cs b/src/Apollinare.Infrastructure/Data/AppollinareDbContext.cs index c11d2a9..38a0607 100644 --- a/src/Apollinare.Infrastructure/Data/AppollinareDbContext.cs +++ b/src/Apollinare.Infrastructure/Data/AppollinareDbContext.cs @@ -1,5 +1,8 @@ using Apollinare.Domain.Entities; using Apollinare.Domain.Entities.Warehouse; +using Apollinare.Domain.Entities.Purchases; +using Apollinare.Domain.Entities.Sales; +using Apollinare.Domain.Entities.Production; using Microsoft.EntityFrameworkCore; namespace Apollinare.Infrastructure.Data; @@ -63,6 +66,26 @@ public class AppollinareDbContext : DbContext public DbSet InventoryCounts => Set(); public DbSet InventoryCountLines => Set(); + // Purchases module entities + public DbSet Suppliers => Set(); + public DbSet PurchaseOrders => Set(); + public DbSet PurchaseOrderLines => Set(); + + // Sales module entities + public DbSet SalesOrders => Set(); + public DbSet SalesOrderLines => Set(); + + // Production module entities + public DbSet BillOfMaterials => Set(); + public DbSet BillOfMaterialsComponents => Set(); + public DbSet ProductionOrders => Set(); + public DbSet ProductionOrderComponents => Set(); + public DbSet WorkCenters => Set(); + public DbSet ProductionCycles => Set(); + public DbSet ProductionCyclePhases => Set(); + public DbSet ProductionOrderPhases => Set(); + public DbSet MrpSuggestions => Set(); + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -627,5 +650,272 @@ public class AppollinareDbContext : DbContext .HasForeignKey(e => e.BatchId) .OnDelete(DeleteBehavior.SetNull); }); + + // =============================================== + // PURCHASES MODULE ENTITIES + // =============================================== + + // Supplier + modelBuilder.Entity(entity => + { + entity.ToTable("Suppliers"); + entity.HasIndex(e => e.Code).IsUnique(); + entity.HasIndex(e => e.Name); + entity.HasIndex(e => e.VatNumber); + entity.HasIndex(e => e.IsActive); + }); + + // PurchaseOrder + modelBuilder.Entity(entity => + { + entity.ToTable("PurchaseOrders"); + entity.HasIndex(e => e.OrderNumber).IsUnique(); + entity.HasIndex(e => e.OrderDate); + entity.HasIndex(e => e.SupplierId); + entity.HasIndex(e => e.Status); + + entity.Property(e => e.TotalNet).HasPrecision(18, 4); + entity.Property(e => e.TotalTax).HasPrecision(18, 4); + entity.Property(e => e.TotalGross).HasPrecision(18, 4); + + entity.HasOne(e => e.Supplier) + .WithMany(s => s.PurchaseOrders) + .HasForeignKey(e => e.SupplierId) + .OnDelete(DeleteBehavior.Restrict); + + entity.HasOne(e => e.DestinationWarehouse) + .WithMany() + .HasForeignKey(e => e.DestinationWarehouseId) + .OnDelete(DeleteBehavior.SetNull); + }); + + // PurchaseOrderLine + modelBuilder.Entity(entity => + { + entity.ToTable("PurchaseOrderLines"); + entity.HasIndex(e => e.PurchaseOrderId); + entity.HasIndex(e => e.WarehouseArticleId); + + entity.Property(e => e.Quantity).HasPrecision(18, 4); + entity.Property(e => e.ReceivedQuantity).HasPrecision(18, 4); + entity.Property(e => e.UnitPrice).HasPrecision(18, 4); + entity.Property(e => e.TaxRate).HasPrecision(18, 2); + entity.Property(e => e.DiscountPercent).HasPrecision(18, 2); + entity.Property(e => e.LineTotal).HasPrecision(18, 4); + + entity.HasOne(e => e.PurchaseOrder) + .WithMany(o => o.Lines) + .HasForeignKey(e => e.PurchaseOrderId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.WarehouseArticle) + .WithMany() + .HasForeignKey(e => e.WarehouseArticleId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // =============================================== + // SALES MODULE ENTITIES + // =============================================== + + // SalesOrder + modelBuilder.Entity(entity => + { + entity.ToTable("SalesOrders"); + entity.HasIndex(e => e.OrderNumber).IsUnique(); + entity.HasIndex(e => e.OrderDate); + entity.HasIndex(e => e.CustomerId); + entity.HasIndex(e => e.Status); + + entity.Property(e => e.TotalNet).HasPrecision(18, 4); + entity.Property(e => e.TotalTax).HasPrecision(18, 4); + entity.Property(e => e.TotalGross).HasPrecision(18, 4); + + entity.HasOne(e => e.Customer) + .WithMany(c => c.SalesOrders) + .HasForeignKey(e => e.CustomerId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // SalesOrderLine + modelBuilder.Entity(entity => + { + entity.ToTable("SalesOrderLines"); + entity.HasIndex(e => e.SalesOrderId); + entity.HasIndex(e => e.WarehouseArticleId); + + entity.Property(e => e.Quantity).HasPrecision(18, 4); + entity.Property(e => e.ShippedQuantity).HasPrecision(18, 4); + entity.Property(e => e.UnitPrice).HasPrecision(18, 4); + entity.Property(e => e.TaxRate).HasPrecision(18, 2); + entity.Property(e => e.DiscountPercent).HasPrecision(18, 2); + entity.Property(e => e.LineTotal).HasPrecision(18, 4); + + entity.HasOne(e => e.SalesOrder) + .WithMany(o => o.Lines) + .HasForeignKey(e => e.SalesOrderId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.WarehouseArticle) + .WithMany() + .HasForeignKey(e => e.WarehouseArticleId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // =============================================== + // PRODUCTION MODULE ENTITIES + // =============================================== + + // BillOfMaterials + modelBuilder.Entity(entity => + { + entity.ToTable("BillOfMaterials"); + entity.HasIndex(e => e.ArticleId); + entity.HasIndex(e => e.IsActive); + + entity.Property(e => e.Quantity).HasPrecision(18, 4); + + entity.HasOne(e => e.Article) + .WithMany() + .HasForeignKey(e => e.ArticleId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // BillOfMaterialsComponent + modelBuilder.Entity(entity => + { + entity.ToTable("BillOfMaterialsComponents"); + entity.HasIndex(e => e.BillOfMaterialsId); + entity.HasIndex(e => e.ComponentArticleId); + + entity.Property(e => e.Quantity).HasPrecision(18, 4); + entity.Property(e => e.ScrapPercentage).HasPrecision(18, 2); + + entity.HasOne(e => e.BillOfMaterials) + .WithMany(b => b.Components) + .HasForeignKey(e => e.BillOfMaterialsId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.ComponentArticle) + .WithMany() + .HasForeignKey(e => e.ComponentArticleId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // ProductionOrder + modelBuilder.Entity(entity => + { + entity.ToTable("ProductionOrders"); + entity.HasIndex(e => e.Code).IsUnique(); + entity.HasIndex(e => e.ArticleId); + entity.HasIndex(e => e.Status); + entity.HasIndex(e => e.StartDate); + + entity.Property(e => e.Quantity).HasPrecision(18, 4); + + entity.HasOne(e => e.Article) + .WithMany() + .HasForeignKey(e => e.ArticleId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // ProductionOrderComponent + modelBuilder.Entity(entity => + { + entity.ToTable("ProductionOrderComponents"); + entity.HasIndex(e => e.ProductionOrderId); + entity.HasIndex(e => e.ArticleId); + + entity.Property(e => e.RequiredQuantity).HasPrecision(18, 4); + entity.Property(e => e.ConsumedQuantity).HasPrecision(18, 4); + + entity.HasOne(e => e.ProductionOrder) + .WithMany(o => o.Components) + .HasForeignKey(e => e.ProductionOrderId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.Article) + .WithMany() + .HasForeignKey(e => e.ArticleId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // WorkCenter + modelBuilder.Entity(entity => + { + entity.ToTable("WorkCenters"); + entity.HasIndex(e => e.Code).IsUnique(); + entity.HasIndex(e => e.IsActive); + entity.Property(e => e.CostPerHour).HasPrecision(18, 4); + }); + + // ProductionCycle + modelBuilder.Entity(entity => + { + entity.ToTable("ProductionCycles"); + entity.HasIndex(e => e.ArticleId); + entity.HasIndex(e => e.IsActive); + + entity.HasOne(e => e.Article) + .WithMany() + .HasForeignKey(e => e.ArticleId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // ProductionCyclePhase + modelBuilder.Entity(entity => + { + entity.ToTable("ProductionCyclePhases"); + entity.HasIndex(e => e.ProductionCycleId); + entity.HasIndex(e => e.WorkCenterId); + + entity.HasOne(e => e.ProductionCycle) + .WithMany(c => c.Phases) + .HasForeignKey(e => e.ProductionCycleId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.WorkCenter) + .WithMany() + .HasForeignKey(e => e.WorkCenterId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // ProductionOrderPhase + modelBuilder.Entity(entity => + { + entity.ToTable("ProductionOrderPhases"); + entity.HasIndex(e => e.ProductionOrderId); + entity.HasIndex(e => e.WorkCenterId); + entity.HasIndex(e => e.Status); + + entity.Property(e => e.QuantityCompleted).HasPrecision(18, 4); + entity.Property(e => e.QuantityScrapped).HasPrecision(18, 4); + + entity.HasOne(e => e.ProductionOrder) + .WithMany(o => o.Phases) + .HasForeignKey(e => e.ProductionOrderId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.WorkCenter) + .WithMany() + .HasForeignKey(e => e.WorkCenterId) + .OnDelete(DeleteBehavior.Restrict); + }); + + // MrpSuggestion + modelBuilder.Entity(entity => + { + entity.ToTable("MrpSuggestions"); + entity.HasIndex(e => e.ArticleId); + entity.HasIndex(e => e.CalculationDate); + entity.HasIndex(e => e.IsProcessed); + + entity.Property(e => e.Quantity).HasPrecision(18, 4); + + entity.HasOne(e => e.Article) + .WithMany() + .HasForeignKey(e => e.ArticleId) + .OnDelete(DeleteBehavior.Cascade); + }); } } diff --git a/src/Apollinare.Infrastructure/Migrations/20251130134233_AddPurchasesModule.Designer.cs b/src/Apollinare.Infrastructure/Migrations/20251130134233_AddPurchasesModule.Designer.cs new file mode 100644 index 0000000..896994d --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251130134233_AddPurchasesModule.Designer.cs @@ -0,0 +1,3570 @@ +// +using System; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + [DbContext(typeof(AppollinareDbContext))] + [Migration("20251130134233_AddPurchasesModule")] + partial class AddPurchasesModule + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); + + modelBuilder.Entity("Apollinare.Domain.Entities.AppModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BasePrice") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Dependencies") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("IsAvailable") + .HasColumnType("INTEGER"); + + b.Property("IsCore") + .HasColumnType("INTEGER"); + + b.Property("MonthlyMultiplier") + .HasPrecision(5, 2) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RoutePath") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("SortOrder"); + + b.ToTable("AppModules"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("CategoriaId") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Immagine") + .HasColumnType("BLOB"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("QtaDisponibile") + .HasColumnType("TEXT"); + + b.Property("QtaStdA") + .HasColumnType("TEXT"); + + b.Property("QtaStdB") + .HasColumnType("TEXT"); + + b.Property("QtaStdS") + .HasColumnType("TEXT"); + + b.Property("TipoMaterialeId") + .HasColumnType("INTEGER"); + + b.Property("UnitaMisura") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CategoriaId"); + + b.HasIndex("Codice") + .IsUnique(); + + b.HasIndex("TipoMaterialeId"); + + b.ToTable("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.AutoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EntityCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EntityName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsReadOnly") + .HasColumnType("INTEGER"); + + b.Property("LastResetMonth") + .HasColumnType("INTEGER"); + + b.Property("LastResetYear") + .HasColumnType("INTEGER"); + + b.Property("LastSequence") + .HasColumnType("INTEGER"); + + b.Property("ModuleCode") + .HasColumnType("TEXT"); + + b.Property("Pattern") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Prefix") + .HasColumnType("TEXT"); + + b.Property("ResetSequenceMonthly") + .HasColumnType("INTEGER"); + + b.Property("ResetSequenceYearly") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntityCode") + .IsUnique(); + + b.HasIndex("ModuleCode"); + + b.ToTable("AutoCodes"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cap") + .HasColumnType("TEXT"); + + b.Property("Citta") + .HasColumnType("TEXT"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + + b.Property("CodiceDestinatario") + .HasColumnType("TEXT"); + + b.Property("CodiceFiscale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Indirizzo") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("PartitaIva") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Provincia") + .HasColumnType("TEXT"); + + b.Property("RagioneSociale") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartitaIva"); + + b.HasIndex("RagioneSociale"); + + b.ToTable("Clienti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CoeffA") + .HasColumnType("TEXT"); + + b.Property("CoeffB") + .HasColumnType("TEXT"); + + b.Property("CoeffS") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CodiciCategoria"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Configurazione", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Chiave") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Valore") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Chiave") + .IsUnique(); + + b.ToTable("Configurazioni"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CustomFieldDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DefaultValue") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EntityName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsRequired") + .HasColumnType("INTEGER"); + + b.Property("Label") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OptionsJson") + .HasColumnType("TEXT"); + + b.Property("Placeholder") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValidationRegex") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntityName"); + + b.HasIndex("EntityName", "FieldName") + .IsUnique(); + + b.ToTable("CustomFieldDefinitions"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClienteId") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .HasColumnType("TEXT"); + + b.Property("Confermato") + .HasColumnType("INTEGER"); + + b.Property("CostoPersona") + .HasColumnType("TEXT"); + + b.Property("CostoTotale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataEvento") + .HasColumnType("TEXT"); + + b.Property("DataScadenzaPreventivo") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("NoteAllestimento") + .HasColumnType("TEXT"); + + b.Property("NoteCliente") + .HasColumnType("TEXT"); + + b.Property("NoteCucina") + .HasColumnType("TEXT"); + + b.Property("NoteInterne") + .HasColumnType("TEXT"); + + b.Property("NumeroOspiti") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiAdulti") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiBambini") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiBuffet") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiSeduti") + .HasColumnType("INTEGER"); + + b.Property("OraFine") + .HasColumnType("TEXT"); + + b.Property("OraInizio") + .HasColumnType("TEXT"); + + b.Property("Saldo") + .HasColumnType("TEXT"); + + b.Property("Stato") + .HasColumnType("INTEGER"); + + b.Property("TipoEventoId") + .HasColumnType("INTEGER"); + + b.Property("TotaleAcconti") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClienteId"); + + b.HasIndex("Codice"); + + b.HasIndex("DataEvento"); + + b.HasIndex("LocationId"); + + b.HasIndex("Stato"); + + b.HasIndex("TipoEventoId"); + + b.ToTable("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAcconto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AConferma") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataPagamento") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Importo") + .HasColumnType("TEXT"); + + b.Property("MetodoPagamento") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAcconti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAllegato", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Contenuto") + .HasColumnType("BLOB"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("NomeFile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAllegati"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAltroCosto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AliquotaIva") + .HasColumnType("TEXT"); + + b.Property("ApplicaIva") + .HasColumnType("INTEGER"); + + b.Property("CostoUnitario") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("Quantita") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAltriCosti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDegustazione", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completata") + .HasColumnType("INTEGER"); + + b.Property("CostoDegustazione") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataDegustazione") + .HasColumnType("TEXT"); + + b.Property("Detraibile") + .HasColumnType("INTEGER"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Luogo") + .HasColumnType("TEXT"); + + b.Property("Menu") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("NumeroPaganti") + .HasColumnType("INTEGER"); + + b.Property("NumeroPersone") + .HasColumnType("INTEGER"); + + b.Property("Ora") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiDegustazioni"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioOspiti", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CostoUnitario") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Numero") + .HasColumnType("INTEGER"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("Sconto") + .HasColumnType("TEXT"); + + b.Property("TipoOspiteId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.HasIndex("TipoOspiteId"); + + b.ToTable("EventiDettaglioOspiti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioPrelievo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticoloId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("QtaCalcolata") + .HasColumnType("TEXT"); + + b.Property("QtaEffettiva") + .HasColumnType("TEXT"); + + b.Property("QtaRichiesta") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticoloId"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiDettaglioPrelievo"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioRisorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Costo") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OraFine") + .HasColumnType("TEXT"); + + b.Property("OraInizio") + .HasColumnType("TEXT"); + + b.Property("OreLavoro") + .HasColumnType("TEXT"); + + b.Property("RisorsaId") + .HasColumnType("INTEGER"); + + b.Property("Ruolo") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.HasIndex("RisorsaId"); + + b.ToTable("EventiDettaglioRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cap") + .HasColumnType("TEXT"); + + b.Property("Citta") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DistanzaKm") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Indirizzo") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Provincia") + .HasColumnType("TEXT"); + + b.Property("Referente") + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Nome"); + + b.ToTable("Location"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ModuleSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoRenew") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastRenewalDate") + .HasColumnType("TEXT"); + + b.Property("ModuleId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaidPrice") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("SubscriptionType") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ModuleId") + .IsUnique(); + + b.ToTable("ModuleSubscriptions"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SupplierId"); + + b.ToTable("PurchaseOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("PurchaseOrderId") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReceivedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("PurchaseOrderLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("FiscalCode") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaymentTerms") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("VatNumber") + .HasColumnType("TEXT"); + + b.Property("Website") + .HasColumnType("TEXT"); + + b.Property("ZipCode") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("Name"); + + b.HasIndex("VatNumber"); + + b.ToTable("Suppliers", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportFont", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("FontData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("FontFamily") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FontStyle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FontFamily"); + + b.ToTable("ReportFonts"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("ImageData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.ToTable("ReportImages"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Orientation") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PageSize") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TemplateJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Thumbnail") + .HasColumnType("BLOB"); + + b.Property("ThumbnailMimeType") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.HasIndex("Nome"); + + b.ToTable("ReportTemplates"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cognome") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("TipoRisorsaId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TipoRisorsaId"); + + b.ToTable("Risorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TipoPastoId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TipoPastoId"); + + b.ToTable("TipiEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoMateriale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiMateriale"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoOspite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiOspite"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoPasto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiPasto"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoRisorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiRisorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Utente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cognome") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Nome") + .HasColumnType("TEXT"); + + b.Property("Ruolo") + .HasColumnType("TEXT"); + + b.Property("SolaLettura") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Utenti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.VirtualDataset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("VirtualDatasets"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBarcode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Barcode") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.ToTable("ArticleBarcodes", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Certifications") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CurrentQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("ExpiryDate") + .HasColumnType("TEXT"); + + b.Property("InitialQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("LastQualityCheckDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ProductionDate") + .HasColumnType("TEXT"); + + b.Property("QualityStatus") + .HasColumnType("INTEGER"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierBatch") + .HasColumnType("TEXT"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("Status"); + + b.HasIndex("ArticleId", "BatchNumber") + .IsUnique(); + + b.ToTable("ArticleBatches", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Attributes") + .HasColumnType("TEXT"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CurrentWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("ManufacturerSerial") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ProductionDate") + .HasColumnType("TEXT"); + + b.Property("SalesReference") + .HasColumnType("TEXT"); + + b.Property("SerialNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SoldDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarrantyExpiryDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("CurrentWarehouseId"); + + b.HasIndex("Status"); + + b.HasIndex("ArticleId", "SerialNumber") + .IsUnique(); + + b.ToTable("ArticleSerials", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdjustmentMovementId") + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("ConfirmedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("InventoryDate") + .HasColumnType("TEXT"); + + b.Property("NegativeDifferenceValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PositiveDifferenceValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AdjustmentMovementId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("InventoryDate"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("InventoryCounts", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCountLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CountedAt") + .HasColumnType("TEXT"); + + b.Property("CountedBy") + .HasColumnType("TEXT"); + + b.Property("CountedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("InventoryCountId") + .HasColumnType("INTEGER"); + + b.Property("LocationCode") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("SecondCountBy") + .HasColumnType("TEXT"); + + b.Property("SecondCountQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TheoreticalQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("BatchId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("InventoryCountId", "ArticleId", "WarehouseId", "BatchId") + .IsUnique(); + + b.ToTable("InventoryCountLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.MovementReason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsSystem") + .HasColumnType("INTEGER"); + + b.Property("MovementType") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("RequiresExternalReference") + .HasColumnType("INTEGER"); + + b.Property("RequiresValuation") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("StockSign") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("UpdatesAverageCost") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("MovementType"); + + b.ToTable("MovementReasons", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockLevel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("LastInventoryDate") + .HasColumnType("TEXT"); + + b.Property("LastMovementDate") + .HasColumnType("TEXT"); + + b.Property("LocationCode") + .HasColumnType("TEXT"); + + b.Property("OnOrderQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StockValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("LocationCode"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ArticleId", "WarehouseId", "BatchId") + .IsUnique(); + + b.ToTable("StockLevels", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("ConfirmedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("DocumentNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExternalDocumentType") + .HasColumnType("INTEGER"); + + b.Property("ExternalReference") + .HasColumnType("TEXT"); + + b.Property("MovementDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReasonId") + .HasColumnType("INTEGER"); + + b.Property("SourceWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("DocumentNumber") + .IsUnique(); + + b.HasIndex("ExternalReference"); + + b.HasIndex("MovementDate"); + + b.HasIndex("ReasonId"); + + b.HasIndex("SourceWarehouseId"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("StockMovements", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovementLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationLocationCode") + .HasColumnType("TEXT"); + + b.Property("ExternalLineReference") + .HasColumnType("TEXT"); + + b.Property("LineNumber") + .HasColumnType("INTEGER"); + + b.Property("LineValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("MovementId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SerialId") + .HasColumnType("INTEGER"); + + b.Property("SourceLocationCode") + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("MovementId", "LineNumber") + .IsUnique(); + + b.ToTable("StockMovementLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("ClosedBy") + .HasColumnType("TEXT"); + + b.Property("ClosedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("InboundQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("InboundValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("IsClosed") + .HasColumnType("INTEGER"); + + b.Property("Method") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OutboundQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("OutboundValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Period") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValuationDate") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsClosed"); + + b.HasIndex("ValuationDate"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("Period", "ArticleId", "WarehouseId") + .IsUnique(); + + b.ToTable("StockValuations", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuationLayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("IsExhausted") + .HasColumnType("INTEGER"); + + b.Property("LayerDate") + .HasColumnType("TEXT"); + + b.Property("OriginalQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("RemainingQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SourceMovementId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("IsExhausted"); + + b.HasIndex("SourceMovementId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ArticleId", "WarehouseId", "LayerDate"); + + b.ToTable("StockValuationLayers", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("Barcode") + .HasColumnType("TEXT"); + + b.Property("BaseSellingPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Depth") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiryWarningDays") + .HasColumnType("INTEGER"); + + b.Property("HasExpiry") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("TEXT"); + + b.Property("Image") + .HasColumnType("BLOB"); + + b.Property("ImageMimeType") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsBatchManaged") + .HasColumnType("INTEGER"); + + b.Property("IsSerialManaged") + .HasColumnType("INTEGER"); + + b.Property("LastPurchaseCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("LeadTimeDays") + .HasColumnType("INTEGER"); + + b.Property("ManufacturerCode") + .HasColumnType("TEXT"); + + b.Property("MaximumStock") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("MinimumStock") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReorderQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SecondaryUnitOfMeasure") + .HasColumnType("TEXT"); + + b.Property("ShortDescription") + .HasColumnType("TEXT"); + + b.Property("StandardCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StockManagement") + .HasColumnType("INTEGER"); + + b.Property("UnitConversionFactor") + .HasPrecision(18, 6) + .HasColumnType("TEXT"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValuationMethod") + .HasColumnType("INTEGER"); + + b.Property("Volume") + .HasPrecision(18, 6) + .HasColumnType("TEXT"); + + b.Property("Weight") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("WeightedAverageCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Barcode"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.ToTable("WarehouseArticles", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Color") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DefaultValuationMethod") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FullPath") + .HasColumnType("TEXT"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ParentCategoryId") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("FullPath"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("WarehouseArticleCategories", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("IsDefault"); + + b.ToTable("WarehouseLocations", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.HasOne("Apollinare.Domain.Entities.CodiceCategoria", "Categoria") + .WithMany("Articoli") + .HasForeignKey("CategoriaId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.TipoMateriale", "TipoMateriale") + .WithMany("Articoli") + .HasForeignKey("TipoMaterialeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Categoria"); + + b.Navigation("TipoMateriale"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.HasOne("Apollinare.Domain.Entities.Cliente", "Cliente") + .WithMany("Eventi") + .HasForeignKey("ClienteId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Location", "Location") + .WithMany("Eventi") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.TipoEvento", "TipoEvento") + .WithMany("Eventi") + .HasForeignKey("TipoEventoId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cliente"); + + b.Navigation("Location"); + + b.Navigation("TipoEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAcconto", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Acconti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAllegato", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Allegati") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAltroCosto", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("AltriCosti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDegustazione", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Degustazioni") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioOspiti", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliOspiti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.TipoOspite", "TipoOspite") + .WithMany("DettagliOspiti") + .HasForeignKey("TipoOspiteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + + b.Navigation("TipoOspite"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioPrelievo", b => + { + b.HasOne("Apollinare.Domain.Entities.Articolo", "Articolo") + .WithMany("DettagliPrelievo") + .HasForeignKey("ArticoloId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliPrelievo") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Articolo"); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioRisorsa", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliRisorse") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Risorsa", "Risorsa") + .WithMany("DettagliRisorse") + .HasForeignKey("RisorsaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + + b.Navigation("Risorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ModuleSubscription", b => + { + b.HasOne("Apollinare.Domain.Entities.AppModule", "Module") + .WithOne("Subscription") + .HasForeignKey("Apollinare.Domain.Entities.ModuleSubscription", "ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany() + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Purchases.Supplier", "Supplier") + .WithMany("PurchaseOrders") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Purchases.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PurchaseOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("WarehouseArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.HasOne("Apollinare.Domain.Entities.TipoRisorsa", "TipoRisorsa") + .WithMany("Risorse") + .HasForeignKey("TipoRisorsaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("TipoRisorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.HasOne("Apollinare.Domain.Entities.TipoPasto", "TipoPasto") + .WithMany("TipiEvento") + .HasForeignKey("TipoPastoId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("TipoPasto"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBarcode", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Barcodes") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Batches") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Serials") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("Serials") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "CurrentWarehouse") + .WithMany() + .HasForeignKey("CurrentWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("CurrentWarehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "AdjustmentMovement") + .WithMany() + .HasForeignKey("AdjustmentMovementId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("AdjustmentMovement"); + + b.Navigation("Category"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCountLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.InventoryCount", "InventoryCount") + .WithMany("Lines") + .HasForeignKey("InventoryCountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("InventoryCount"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockLevel", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("StockLevels") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("StockLevels") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany("StockLevels") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany("DestinationMovements") + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.MovementReason", "Reason") + .WithMany("Movements") + .HasForeignKey("ReasonId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "SourceWarehouse") + .WithMany("SourceMovements") + .HasForeignKey("SourceWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Reason"); + + b.Navigation("SourceWarehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovementLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("MovementLines") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("MovementLines") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "Movement") + .WithMany("Lines") + .HasForeignKey("MovementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleSerial", "Serial") + .WithMany("MovementLines") + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("Movement"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuation", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuationLayer", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "SourceMovement") + .WithMany() + .HasForeignKey("SourceMovementId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("SourceMovement"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "Category") + .WithMany("Articles") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.AppModule", b => + { + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.Navigation("DettagliPrelievo"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => + { + b.Navigation("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.Navigation("Acconti"); + + b.Navigation("Allegati"); + + b.Navigation("AltriCosti"); + + b.Navigation("Degustazioni"); + + b.Navigation("DettagliOspiti"); + + b.Navigation("DettagliPrelievo"); + + b.Navigation("DettagliRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Location", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Navigation("PurchaseOrders"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.Navigation("DettagliRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoMateriale", b => + { + b.Navigation("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoOspite", b => + { + b.Navigation("DettagliOspiti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoPasto", b => + { + b.Navigation("TipiEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoRisorsa", b => + { + b.Navigation("Risorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.Navigation("MovementLines"); + + b.Navigation("Serials"); + + b.Navigation("StockLevels"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.Navigation("MovementLines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.MovementReason", b => + { + b.Navigation("Movements"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.Navigation("Barcodes"); + + b.Navigation("Batches"); + + b.Navigation("MovementLines"); + + b.Navigation("Serials"); + + b.Navigation("StockLevels"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.Navigation("Articles"); + + b.Navigation("ChildCategories"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", b => + { + b.Navigation("DestinationMovements"); + + b.Navigation("SourceMovements"); + + b.Navigation("StockLevels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/20251130134233_AddPurchasesModule.cs b/src/Apollinare.Infrastructure/Migrations/20251130134233_AddPurchasesModule.cs new file mode 100644 index 0000000..a7428a4 --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251130134233_AddPurchasesModule.cs @@ -0,0 +1,195 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + /// + public partial class AddPurchasesModule : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Suppliers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Code = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + VatNumber = table.Column(type: "TEXT", nullable: true), + FiscalCode = table.Column(type: "TEXT", nullable: true), + Address = table.Column(type: "TEXT", nullable: true), + City = table.Column(type: "TEXT", nullable: true), + Province = table.Column(type: "TEXT", nullable: true), + ZipCode = table.Column(type: "TEXT", nullable: true), + Country = table.Column(type: "TEXT", nullable: true), + Email = table.Column(type: "TEXT", nullable: true), + Pec = table.Column(type: "TEXT", nullable: true), + Phone = table.Column(type: "TEXT", nullable: true), + Website = table.Column(type: "TEXT", nullable: true), + PaymentTerms = table.Column(type: "TEXT", nullable: true), + Notes = table.Column(type: "TEXT", nullable: true), + IsActive = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Suppliers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PurchaseOrders", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + OrderNumber = table.Column(type: "TEXT", nullable: false), + OrderDate = table.Column(type: "TEXT", nullable: false), + ExpectedDeliveryDate = table.Column(type: "TEXT", nullable: true), + SupplierId = table.Column(type: "INTEGER", nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + DestinationWarehouseId = table.Column(type: "INTEGER", nullable: true), + Notes = table.Column(type: "TEXT", nullable: true), + TotalNet = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + TotalTax = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + TotalGross = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseOrders", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseOrders_Suppliers_SupplierId", + column: x => x.SupplierId, + principalTable: "Suppliers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_PurchaseOrders_WarehouseLocations_DestinationWarehouseId", + column: x => x.DestinationWarehouseId, + principalTable: "WarehouseLocations", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "PurchaseOrderLines", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PurchaseOrderId = table.Column(type: "INTEGER", nullable: false), + WarehouseArticleId = table.Column(type: "INTEGER", nullable: false), + Description = table.Column(type: "TEXT", nullable: false), + Quantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + ReceivedQuantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + UnitPrice = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + TaxRate = table.Column(type: "TEXT", precision: 18, scale: 2, nullable: false), + DiscountPercent = table.Column(type: "TEXT", precision: 18, scale: 2, nullable: false), + LineTotal = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseOrderLines", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseOrderLines_PurchaseOrders_PurchaseOrderId", + column: x => x.PurchaseOrderId, + principalTable: "PurchaseOrders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PurchaseOrderLines_WarehouseArticles_WarehouseArticleId", + column: x => x.WarehouseArticleId, + principalTable: "WarehouseArticles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseOrderLines_PurchaseOrderId", + table: "PurchaseOrderLines", + column: "PurchaseOrderId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseOrderLines_WarehouseArticleId", + table: "PurchaseOrderLines", + column: "WarehouseArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseOrders_DestinationWarehouseId", + table: "PurchaseOrders", + column: "DestinationWarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseOrders_OrderDate", + table: "PurchaseOrders", + column: "OrderDate"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseOrders_OrderNumber", + table: "PurchaseOrders", + column: "OrderNumber", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseOrders_Status", + table: "PurchaseOrders", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseOrders_SupplierId", + table: "PurchaseOrders", + column: "SupplierId"); + + migrationBuilder.CreateIndex( + name: "IX_Suppliers_Code", + table: "Suppliers", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Suppliers_IsActive", + table: "Suppliers", + column: "IsActive"); + + migrationBuilder.CreateIndex( + name: "IX_Suppliers_Name", + table: "Suppliers", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_Suppliers_VatNumber", + table: "Suppliers", + column: "VatNumber"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PurchaseOrderLines"); + + migrationBuilder.DropTable( + name: "PurchaseOrders"); + + migrationBuilder.DropTable( + name: "Suppliers"); + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/20251130143646_AddSalesModule.Designer.cs b/src/Apollinare.Infrastructure/Migrations/20251130143646_AddSalesModule.Designer.cs new file mode 100644 index 0000000..6874a76 --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251130143646_AddSalesModule.Designer.cs @@ -0,0 +1,3737 @@ +// +using System; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + [DbContext(typeof(AppollinareDbContext))] + [Migration("20251130143646_AddSalesModule")] + partial class AddSalesModule + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); + + modelBuilder.Entity("Apollinare.Domain.Entities.AppModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BasePrice") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Dependencies") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("IsAvailable") + .HasColumnType("INTEGER"); + + b.Property("IsCore") + .HasColumnType("INTEGER"); + + b.Property("MonthlyMultiplier") + .HasPrecision(5, 2) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RoutePath") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("SortOrder"); + + b.ToTable("AppModules"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("CategoriaId") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Immagine") + .HasColumnType("BLOB"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("QtaDisponibile") + .HasColumnType("TEXT"); + + b.Property("QtaStdA") + .HasColumnType("TEXT"); + + b.Property("QtaStdB") + .HasColumnType("TEXT"); + + b.Property("QtaStdS") + .HasColumnType("TEXT"); + + b.Property("TipoMaterialeId") + .HasColumnType("INTEGER"); + + b.Property("UnitaMisura") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CategoriaId"); + + b.HasIndex("Codice") + .IsUnique(); + + b.HasIndex("TipoMaterialeId"); + + b.ToTable("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.AutoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EntityCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EntityName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsReadOnly") + .HasColumnType("INTEGER"); + + b.Property("LastResetMonth") + .HasColumnType("INTEGER"); + + b.Property("LastResetYear") + .HasColumnType("INTEGER"); + + b.Property("LastSequence") + .HasColumnType("INTEGER"); + + b.Property("ModuleCode") + .HasColumnType("TEXT"); + + b.Property("Pattern") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Prefix") + .HasColumnType("TEXT"); + + b.Property("ResetSequenceMonthly") + .HasColumnType("INTEGER"); + + b.Property("ResetSequenceYearly") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntityCode") + .IsUnique(); + + b.HasIndex("ModuleCode"); + + b.ToTable("AutoCodes"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cap") + .HasColumnType("TEXT"); + + b.Property("Citta") + .HasColumnType("TEXT"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + + b.Property("CodiceDestinatario") + .HasColumnType("TEXT"); + + b.Property("CodiceFiscale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Indirizzo") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("PartitaIva") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Provincia") + .HasColumnType("TEXT"); + + b.Property("RagioneSociale") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartitaIva"); + + b.HasIndex("RagioneSociale"); + + b.ToTable("Clienti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CoeffA") + .HasColumnType("TEXT"); + + b.Property("CoeffB") + .HasColumnType("TEXT"); + + b.Property("CoeffS") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CodiciCategoria"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Configurazione", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Chiave") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Valore") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Chiave") + .IsUnique(); + + b.ToTable("Configurazioni"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CustomFieldDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DefaultValue") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EntityName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsRequired") + .HasColumnType("INTEGER"); + + b.Property("Label") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OptionsJson") + .HasColumnType("TEXT"); + + b.Property("Placeholder") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValidationRegex") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntityName"); + + b.HasIndex("EntityName", "FieldName") + .IsUnique(); + + b.ToTable("CustomFieldDefinitions"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClienteId") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .HasColumnType("TEXT"); + + b.Property("Confermato") + .HasColumnType("INTEGER"); + + b.Property("CostoPersona") + .HasColumnType("TEXT"); + + b.Property("CostoTotale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataEvento") + .HasColumnType("TEXT"); + + b.Property("DataScadenzaPreventivo") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("NoteAllestimento") + .HasColumnType("TEXT"); + + b.Property("NoteCliente") + .HasColumnType("TEXT"); + + b.Property("NoteCucina") + .HasColumnType("TEXT"); + + b.Property("NoteInterne") + .HasColumnType("TEXT"); + + b.Property("NumeroOspiti") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiAdulti") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiBambini") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiBuffet") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiSeduti") + .HasColumnType("INTEGER"); + + b.Property("OraFine") + .HasColumnType("TEXT"); + + b.Property("OraInizio") + .HasColumnType("TEXT"); + + b.Property("Saldo") + .HasColumnType("TEXT"); + + b.Property("Stato") + .HasColumnType("INTEGER"); + + b.Property("TipoEventoId") + .HasColumnType("INTEGER"); + + b.Property("TotaleAcconti") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClienteId"); + + b.HasIndex("Codice"); + + b.HasIndex("DataEvento"); + + b.HasIndex("LocationId"); + + b.HasIndex("Stato"); + + b.HasIndex("TipoEventoId"); + + b.ToTable("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAcconto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AConferma") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataPagamento") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Importo") + .HasColumnType("TEXT"); + + b.Property("MetodoPagamento") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAcconti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAllegato", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Contenuto") + .HasColumnType("BLOB"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("NomeFile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAllegati"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAltroCosto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AliquotaIva") + .HasColumnType("TEXT"); + + b.Property("ApplicaIva") + .HasColumnType("INTEGER"); + + b.Property("CostoUnitario") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("Quantita") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAltriCosti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDegustazione", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completata") + .HasColumnType("INTEGER"); + + b.Property("CostoDegustazione") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataDegustazione") + .HasColumnType("TEXT"); + + b.Property("Detraibile") + .HasColumnType("INTEGER"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Luogo") + .HasColumnType("TEXT"); + + b.Property("Menu") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("NumeroPaganti") + .HasColumnType("INTEGER"); + + b.Property("NumeroPersone") + .HasColumnType("INTEGER"); + + b.Property("Ora") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiDegustazioni"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioOspiti", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CostoUnitario") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Numero") + .HasColumnType("INTEGER"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("Sconto") + .HasColumnType("TEXT"); + + b.Property("TipoOspiteId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.HasIndex("TipoOspiteId"); + + b.ToTable("EventiDettaglioOspiti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioPrelievo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticoloId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("QtaCalcolata") + .HasColumnType("TEXT"); + + b.Property("QtaEffettiva") + .HasColumnType("TEXT"); + + b.Property("QtaRichiesta") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticoloId"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiDettaglioPrelievo"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioRisorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Costo") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OraFine") + .HasColumnType("TEXT"); + + b.Property("OraInizio") + .HasColumnType("TEXT"); + + b.Property("OreLavoro") + .HasColumnType("TEXT"); + + b.Property("RisorsaId") + .HasColumnType("INTEGER"); + + b.Property("Ruolo") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.HasIndex("RisorsaId"); + + b.ToTable("EventiDettaglioRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cap") + .HasColumnType("TEXT"); + + b.Property("Citta") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DistanzaKm") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Indirizzo") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Provincia") + .HasColumnType("TEXT"); + + b.Property("Referente") + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Nome"); + + b.ToTable("Location"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ModuleSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoRenew") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastRenewalDate") + .HasColumnType("TEXT"); + + b.Property("ModuleId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaidPrice") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("SubscriptionType") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ModuleId") + .IsUnique(); + + b.ToTable("ModuleSubscriptions"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SupplierId"); + + b.ToTable("PurchaseOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("PurchaseOrderId") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReceivedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("PurchaseOrderLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("FiscalCode") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaymentTerms") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("VatNumber") + .HasColumnType("TEXT"); + + b.Property("Website") + .HasColumnType("TEXT"); + + b.Property("ZipCode") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("Name"); + + b.HasIndex("VatNumber"); + + b.ToTable("Suppliers", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportFont", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("FontData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("FontFamily") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FontStyle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FontFamily"); + + b.ToTable("ReportFonts"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("ImageData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.ToTable("ReportImages"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Orientation") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PageSize") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TemplateJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Thumbnail") + .HasColumnType("BLOB"); + + b.Property("ThumbnailMimeType") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.HasIndex("Nome"); + + b.ToTable("ReportTemplates"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cognome") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("TipoRisorsaId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TipoRisorsaId"); + + b.ToTable("Risorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("SalesOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SalesOrderId") + .HasColumnType("INTEGER"); + + b.Property("ShippedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SalesOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("SalesOrderLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TipoPastoId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TipoPastoId"); + + b.ToTable("TipiEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoMateriale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiMateriale"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoOspite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiOspite"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoPasto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiPasto"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoRisorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiRisorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Utente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cognome") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Nome") + .HasColumnType("TEXT"); + + b.Property("Ruolo") + .HasColumnType("TEXT"); + + b.Property("SolaLettura") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Utenti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.VirtualDataset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("VirtualDatasets"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBarcode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Barcode") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.ToTable("ArticleBarcodes", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Certifications") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CurrentQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("ExpiryDate") + .HasColumnType("TEXT"); + + b.Property("InitialQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("LastQualityCheckDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ProductionDate") + .HasColumnType("TEXT"); + + b.Property("QualityStatus") + .HasColumnType("INTEGER"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierBatch") + .HasColumnType("TEXT"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("Status"); + + b.HasIndex("ArticleId", "BatchNumber") + .IsUnique(); + + b.ToTable("ArticleBatches", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Attributes") + .HasColumnType("TEXT"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CurrentWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("ManufacturerSerial") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ProductionDate") + .HasColumnType("TEXT"); + + b.Property("SalesReference") + .HasColumnType("TEXT"); + + b.Property("SerialNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SoldDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarrantyExpiryDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("CurrentWarehouseId"); + + b.HasIndex("Status"); + + b.HasIndex("ArticleId", "SerialNumber") + .IsUnique(); + + b.ToTable("ArticleSerials", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdjustmentMovementId") + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("ConfirmedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("InventoryDate") + .HasColumnType("TEXT"); + + b.Property("NegativeDifferenceValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PositiveDifferenceValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AdjustmentMovementId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("InventoryDate"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("InventoryCounts", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCountLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CountedAt") + .HasColumnType("TEXT"); + + b.Property("CountedBy") + .HasColumnType("TEXT"); + + b.Property("CountedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("InventoryCountId") + .HasColumnType("INTEGER"); + + b.Property("LocationCode") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("SecondCountBy") + .HasColumnType("TEXT"); + + b.Property("SecondCountQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TheoreticalQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("BatchId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("InventoryCountId", "ArticleId", "WarehouseId", "BatchId") + .IsUnique(); + + b.ToTable("InventoryCountLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.MovementReason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsSystem") + .HasColumnType("INTEGER"); + + b.Property("MovementType") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("RequiresExternalReference") + .HasColumnType("INTEGER"); + + b.Property("RequiresValuation") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("StockSign") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("UpdatesAverageCost") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("MovementType"); + + b.ToTable("MovementReasons", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockLevel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("LastInventoryDate") + .HasColumnType("TEXT"); + + b.Property("LastMovementDate") + .HasColumnType("TEXT"); + + b.Property("LocationCode") + .HasColumnType("TEXT"); + + b.Property("OnOrderQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StockValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("LocationCode"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ArticleId", "WarehouseId", "BatchId") + .IsUnique(); + + b.ToTable("StockLevels", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("ConfirmedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("DocumentNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExternalDocumentType") + .HasColumnType("INTEGER"); + + b.Property("ExternalReference") + .HasColumnType("TEXT"); + + b.Property("MovementDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReasonId") + .HasColumnType("INTEGER"); + + b.Property("SourceWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("DocumentNumber") + .IsUnique(); + + b.HasIndex("ExternalReference"); + + b.HasIndex("MovementDate"); + + b.HasIndex("ReasonId"); + + b.HasIndex("SourceWarehouseId"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("StockMovements", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovementLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationLocationCode") + .HasColumnType("TEXT"); + + b.Property("ExternalLineReference") + .HasColumnType("TEXT"); + + b.Property("LineNumber") + .HasColumnType("INTEGER"); + + b.Property("LineValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("MovementId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SerialId") + .HasColumnType("INTEGER"); + + b.Property("SourceLocationCode") + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("MovementId", "LineNumber") + .IsUnique(); + + b.ToTable("StockMovementLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("ClosedBy") + .HasColumnType("TEXT"); + + b.Property("ClosedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("InboundQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("InboundValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("IsClosed") + .HasColumnType("INTEGER"); + + b.Property("Method") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OutboundQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("OutboundValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Period") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValuationDate") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsClosed"); + + b.HasIndex("ValuationDate"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("Period", "ArticleId", "WarehouseId") + .IsUnique(); + + b.ToTable("StockValuations", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuationLayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("IsExhausted") + .HasColumnType("INTEGER"); + + b.Property("LayerDate") + .HasColumnType("TEXT"); + + b.Property("OriginalQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("RemainingQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SourceMovementId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("IsExhausted"); + + b.HasIndex("SourceMovementId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ArticleId", "WarehouseId", "LayerDate"); + + b.ToTable("StockValuationLayers", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("Barcode") + .HasColumnType("TEXT"); + + b.Property("BaseSellingPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Depth") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiryWarningDays") + .HasColumnType("INTEGER"); + + b.Property("HasExpiry") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("TEXT"); + + b.Property("Image") + .HasColumnType("BLOB"); + + b.Property("ImageMimeType") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsBatchManaged") + .HasColumnType("INTEGER"); + + b.Property("IsSerialManaged") + .HasColumnType("INTEGER"); + + b.Property("LastPurchaseCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("LeadTimeDays") + .HasColumnType("INTEGER"); + + b.Property("ManufacturerCode") + .HasColumnType("TEXT"); + + b.Property("MaximumStock") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("MinimumStock") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReorderQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SecondaryUnitOfMeasure") + .HasColumnType("TEXT"); + + b.Property("ShortDescription") + .HasColumnType("TEXT"); + + b.Property("StandardCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StockManagement") + .HasColumnType("INTEGER"); + + b.Property("UnitConversionFactor") + .HasPrecision(18, 6) + .HasColumnType("TEXT"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValuationMethod") + .HasColumnType("INTEGER"); + + b.Property("Volume") + .HasPrecision(18, 6) + .HasColumnType("TEXT"); + + b.Property("Weight") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("WeightedAverageCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Barcode"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.ToTable("WarehouseArticles", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Color") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DefaultValuationMethod") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FullPath") + .HasColumnType("TEXT"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ParentCategoryId") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("FullPath"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("WarehouseArticleCategories", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("IsDefault"); + + b.ToTable("WarehouseLocations", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.HasOne("Apollinare.Domain.Entities.CodiceCategoria", "Categoria") + .WithMany("Articoli") + .HasForeignKey("CategoriaId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.TipoMateriale", "TipoMateriale") + .WithMany("Articoli") + .HasForeignKey("TipoMaterialeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Categoria"); + + b.Navigation("TipoMateriale"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.HasOne("Apollinare.Domain.Entities.Cliente", "Cliente") + .WithMany("Eventi") + .HasForeignKey("ClienteId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Location", "Location") + .WithMany("Eventi") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.TipoEvento", "TipoEvento") + .WithMany("Eventi") + .HasForeignKey("TipoEventoId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cliente"); + + b.Navigation("Location"); + + b.Navigation("TipoEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAcconto", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Acconti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAllegato", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Allegati") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAltroCosto", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("AltriCosti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDegustazione", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Degustazioni") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioOspiti", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliOspiti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.TipoOspite", "TipoOspite") + .WithMany("DettagliOspiti") + .HasForeignKey("TipoOspiteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + + b.Navigation("TipoOspite"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioPrelievo", b => + { + b.HasOne("Apollinare.Domain.Entities.Articolo", "Articolo") + .WithMany("DettagliPrelievo") + .HasForeignKey("ArticoloId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliPrelievo") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Articolo"); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioRisorsa", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliRisorse") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Risorsa", "Risorsa") + .WithMany("DettagliRisorse") + .HasForeignKey("RisorsaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + + b.Navigation("Risorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ModuleSubscription", b => + { + b.HasOne("Apollinare.Domain.Entities.AppModule", "Module") + .WithOne("Subscription") + .HasForeignKey("Apollinare.Domain.Entities.ModuleSubscription", "ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany() + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Purchases.Supplier", "Supplier") + .WithMany("PurchaseOrders") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Purchases.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PurchaseOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("WarehouseArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.HasOne("Apollinare.Domain.Entities.TipoRisorsa", "TipoRisorsa") + .WithMany("Risorse") + .HasForeignKey("TipoRisorsaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("TipoRisorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Cliente", "Customer") + .WithMany("SalesOrders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Sales.SalesOrder", "SalesOrder") + .WithMany("Lines") + .HasForeignKey("SalesOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesOrder"); + + b.Navigation("WarehouseArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.HasOne("Apollinare.Domain.Entities.TipoPasto", "TipoPasto") + .WithMany("TipiEvento") + .HasForeignKey("TipoPastoId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("TipoPasto"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBarcode", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Barcodes") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Batches") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Serials") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("Serials") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "CurrentWarehouse") + .WithMany() + .HasForeignKey("CurrentWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("CurrentWarehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "AdjustmentMovement") + .WithMany() + .HasForeignKey("AdjustmentMovementId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("AdjustmentMovement"); + + b.Navigation("Category"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCountLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.InventoryCount", "InventoryCount") + .WithMany("Lines") + .HasForeignKey("InventoryCountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("InventoryCount"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockLevel", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("StockLevels") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("StockLevels") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany("StockLevels") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany("DestinationMovements") + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.MovementReason", "Reason") + .WithMany("Movements") + .HasForeignKey("ReasonId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "SourceWarehouse") + .WithMany("SourceMovements") + .HasForeignKey("SourceWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Reason"); + + b.Navigation("SourceWarehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovementLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("MovementLines") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("MovementLines") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "Movement") + .WithMany("Lines") + .HasForeignKey("MovementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleSerial", "Serial") + .WithMany("MovementLines") + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("Movement"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuation", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuationLayer", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "SourceMovement") + .WithMany() + .HasForeignKey("SourceMovementId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("SourceMovement"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "Category") + .WithMany("Articles") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.AppModule", b => + { + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.Navigation("DettagliPrelievo"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => + { + b.Navigation("Eventi"); + + b.Navigation("SalesOrders"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => + { + b.Navigation("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.Navigation("Acconti"); + + b.Navigation("Allegati"); + + b.Navigation("AltriCosti"); + + b.Navigation("Degustazioni"); + + b.Navigation("DettagliOspiti"); + + b.Navigation("DettagliPrelievo"); + + b.Navigation("DettagliRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Location", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Navigation("PurchaseOrders"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.Navigation("DettagliRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoMateriale", b => + { + b.Navigation("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoOspite", b => + { + b.Navigation("DettagliOspiti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoPasto", b => + { + b.Navigation("TipiEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoRisorsa", b => + { + b.Navigation("Risorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.Navigation("MovementLines"); + + b.Navigation("Serials"); + + b.Navigation("StockLevels"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.Navigation("MovementLines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.MovementReason", b => + { + b.Navigation("Movements"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.Navigation("Barcodes"); + + b.Navigation("Batches"); + + b.Navigation("MovementLines"); + + b.Navigation("Serials"); + + b.Navigation("StockLevels"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.Navigation("Articles"); + + b.Navigation("ChildCategories"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", b => + { + b.Navigation("DestinationMovements"); + + b.Navigation("SourceMovements"); + + b.Navigation("StockLevels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/20251130143646_AddSalesModule.cs b/src/Apollinare.Infrastructure/Migrations/20251130143646_AddSalesModule.cs new file mode 100644 index 0000000..12796de --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251130143646_AddSalesModule.cs @@ -0,0 +1,126 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + /// + public partial class AddSalesModule : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SalesOrders", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + OrderNumber = table.Column(type: "TEXT", nullable: false), + OrderDate = table.Column(type: "TEXT", nullable: false), + ExpectedDeliveryDate = table.Column(type: "TEXT", nullable: true), + CustomerId = table.Column(type: "INTEGER", nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + Notes = table.Column(type: "TEXT", nullable: true), + TotalNet = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + TotalTax = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + TotalGross = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SalesOrders", x => x.Id); + table.ForeignKey( + name: "FK_SalesOrders_Clienti_CustomerId", + column: x => x.CustomerId, + principalTable: "Clienti", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SalesOrderLines", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SalesOrderId = table.Column(type: "INTEGER", nullable: false), + WarehouseArticleId = table.Column(type: "INTEGER", nullable: false), + Description = table.Column(type: "TEXT", nullable: false), + Quantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + ShippedQuantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + UnitPrice = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + TaxRate = table.Column(type: "TEXT", precision: 18, scale: 2, nullable: false), + DiscountPercent = table.Column(type: "TEXT", precision: 18, scale: 2, nullable: false), + LineTotal = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SalesOrderLines", x => x.Id); + table.ForeignKey( + name: "FK_SalesOrderLines_SalesOrders_SalesOrderId", + column: x => x.SalesOrderId, + principalTable: "SalesOrders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SalesOrderLines_WarehouseArticles_WarehouseArticleId", + column: x => x.WarehouseArticleId, + principalTable: "WarehouseArticles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_SalesOrderLines_SalesOrderId", + table: "SalesOrderLines", + column: "SalesOrderId"); + + migrationBuilder.CreateIndex( + name: "IX_SalesOrderLines_WarehouseArticleId", + table: "SalesOrderLines", + column: "WarehouseArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_SalesOrders_CustomerId", + table: "SalesOrders", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_SalesOrders_OrderDate", + table: "SalesOrders", + column: "OrderDate"); + + migrationBuilder.CreateIndex( + name: "IX_SalesOrders_OrderNumber", + table: "SalesOrders", + column: "OrderNumber", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SalesOrders_Status", + table: "SalesOrders", + column: "Status"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SalesOrderLines"); + + migrationBuilder.DropTable( + name: "SalesOrders"); + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/20251130152222_AddProductionModule.Designer.cs b/src/Apollinare.Infrastructure/Migrations/20251130152222_AddProductionModule.Designer.cs new file mode 100644 index 0000000..4a2da37 --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251130152222_AddProductionModule.Designer.cs @@ -0,0 +1,4004 @@ +// +using System; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + [DbContext(typeof(AppollinareDbContext))] + [Migration("20251130152222_AddProductionModule")] + partial class AddProductionModule + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); + + modelBuilder.Entity("Apollinare.Domain.Entities.AppModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BasePrice") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Dependencies") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("IsAvailable") + .HasColumnType("INTEGER"); + + b.Property("IsCore") + .HasColumnType("INTEGER"); + + b.Property("MonthlyMultiplier") + .HasPrecision(5, 2) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RoutePath") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("SortOrder"); + + b.ToTable("AppModules"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("CategoriaId") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Immagine") + .HasColumnType("BLOB"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("QtaDisponibile") + .HasColumnType("TEXT"); + + b.Property("QtaStdA") + .HasColumnType("TEXT"); + + b.Property("QtaStdB") + .HasColumnType("TEXT"); + + b.Property("QtaStdS") + .HasColumnType("TEXT"); + + b.Property("TipoMaterialeId") + .HasColumnType("INTEGER"); + + b.Property("UnitaMisura") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CategoriaId"); + + b.HasIndex("Codice") + .IsUnique(); + + b.HasIndex("TipoMaterialeId"); + + b.ToTable("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.AutoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EntityCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EntityName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsReadOnly") + .HasColumnType("INTEGER"); + + b.Property("LastResetMonth") + .HasColumnType("INTEGER"); + + b.Property("LastResetYear") + .HasColumnType("INTEGER"); + + b.Property("LastSequence") + .HasColumnType("INTEGER"); + + b.Property("ModuleCode") + .HasColumnType("TEXT"); + + b.Property("Pattern") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Prefix") + .HasColumnType("TEXT"); + + b.Property("ResetSequenceMonthly") + .HasColumnType("INTEGER"); + + b.Property("ResetSequenceYearly") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntityCode") + .IsUnique(); + + b.HasIndex("ModuleCode"); + + b.ToTable("AutoCodes"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cap") + .HasColumnType("TEXT"); + + b.Property("Citta") + .HasColumnType("TEXT"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + + b.Property("CodiceDestinatario") + .HasColumnType("TEXT"); + + b.Property("CodiceFiscale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Indirizzo") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("PartitaIva") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Provincia") + .HasColumnType("TEXT"); + + b.Property("RagioneSociale") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartitaIva"); + + b.HasIndex("RagioneSociale"); + + b.ToTable("Clienti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CoeffA") + .HasColumnType("TEXT"); + + b.Property("CoeffB") + .HasColumnType("TEXT"); + + b.Property("CoeffS") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CodiciCategoria"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Configurazione", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Chiave") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Valore") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Chiave") + .IsUnique(); + + b.ToTable("Configurazioni"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CustomFieldDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DefaultValue") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EntityName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsRequired") + .HasColumnType("INTEGER"); + + b.Property("Label") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OptionsJson") + .HasColumnType("TEXT"); + + b.Property("Placeholder") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValidationRegex") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntityName"); + + b.HasIndex("EntityName", "FieldName") + .IsUnique(); + + b.ToTable("CustomFieldDefinitions"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClienteId") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .HasColumnType("TEXT"); + + b.Property("Confermato") + .HasColumnType("INTEGER"); + + b.Property("CostoPersona") + .HasColumnType("TEXT"); + + b.Property("CostoTotale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataEvento") + .HasColumnType("TEXT"); + + b.Property("DataScadenzaPreventivo") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("NoteAllestimento") + .HasColumnType("TEXT"); + + b.Property("NoteCliente") + .HasColumnType("TEXT"); + + b.Property("NoteCucina") + .HasColumnType("TEXT"); + + b.Property("NoteInterne") + .HasColumnType("TEXT"); + + b.Property("NumeroOspiti") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiAdulti") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiBambini") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiBuffet") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiSeduti") + .HasColumnType("INTEGER"); + + b.Property("OraFine") + .HasColumnType("TEXT"); + + b.Property("OraInizio") + .HasColumnType("TEXT"); + + b.Property("Saldo") + .HasColumnType("TEXT"); + + b.Property("Stato") + .HasColumnType("INTEGER"); + + b.Property("TipoEventoId") + .HasColumnType("INTEGER"); + + b.Property("TotaleAcconti") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClienteId"); + + b.HasIndex("Codice"); + + b.HasIndex("DataEvento"); + + b.HasIndex("LocationId"); + + b.HasIndex("Stato"); + + b.HasIndex("TipoEventoId"); + + b.ToTable("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAcconto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AConferma") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataPagamento") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Importo") + .HasColumnType("TEXT"); + + b.Property("MetodoPagamento") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAcconti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAllegato", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Contenuto") + .HasColumnType("BLOB"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("NomeFile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAllegati"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAltroCosto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AliquotaIva") + .HasColumnType("TEXT"); + + b.Property("ApplicaIva") + .HasColumnType("INTEGER"); + + b.Property("CostoUnitario") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("Quantita") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAltriCosti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDegustazione", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completata") + .HasColumnType("INTEGER"); + + b.Property("CostoDegustazione") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataDegustazione") + .HasColumnType("TEXT"); + + b.Property("Detraibile") + .HasColumnType("INTEGER"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Luogo") + .HasColumnType("TEXT"); + + b.Property("Menu") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("NumeroPaganti") + .HasColumnType("INTEGER"); + + b.Property("NumeroPersone") + .HasColumnType("INTEGER"); + + b.Property("Ora") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiDegustazioni"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioOspiti", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CostoUnitario") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Numero") + .HasColumnType("INTEGER"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("Sconto") + .HasColumnType("TEXT"); + + b.Property("TipoOspiteId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.HasIndex("TipoOspiteId"); + + b.ToTable("EventiDettaglioOspiti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioPrelievo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticoloId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("QtaCalcolata") + .HasColumnType("TEXT"); + + b.Property("QtaEffettiva") + .HasColumnType("TEXT"); + + b.Property("QtaRichiesta") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticoloId"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiDettaglioPrelievo"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioRisorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Costo") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OraFine") + .HasColumnType("TEXT"); + + b.Property("OraInizio") + .HasColumnType("TEXT"); + + b.Property("OreLavoro") + .HasColumnType("TEXT"); + + b.Property("RisorsaId") + .HasColumnType("INTEGER"); + + b.Property("Ruolo") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.HasIndex("RisorsaId"); + + b.ToTable("EventiDettaglioRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cap") + .HasColumnType("TEXT"); + + b.Property("Citta") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DistanzaKm") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Indirizzo") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Provincia") + .HasColumnType("TEXT"); + + b.Property("Referente") + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Nome"); + + b.ToTable("Location"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ModuleSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoRenew") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastRenewalDate") + .HasColumnType("TEXT"); + + b.Property("ModuleId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaidPrice") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("SubscriptionType") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ModuleId") + .IsUnique(); + + b.ToTable("ModuleSubscriptions"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsActive"); + + b.ToTable("BillOfMaterials", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterialsComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BillOfMaterialsId") + .HasColumnType("INTEGER"); + + b.Property("ComponentArticleId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ScrapPercentage") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BillOfMaterialsId"); + + b.HasIndex("ComponentArticleId"); + + b.ToTable("BillOfMaterialsComponents", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DueDate") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("StartDate"); + + b.HasIndex("Status"); + + b.ToTable("ProductionOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("ConsumedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("ProductionOrderId") + .HasColumnType("INTEGER"); + + b.Property("RequiredQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("ProductionOrderId"); + + b.ToTable("ProductionOrderComponents", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SupplierId"); + + b.ToTable("PurchaseOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("PurchaseOrderId") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReceivedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("PurchaseOrderLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("FiscalCode") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaymentTerms") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("VatNumber") + .HasColumnType("TEXT"); + + b.Property("Website") + .HasColumnType("TEXT"); + + b.Property("ZipCode") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("Name"); + + b.HasIndex("VatNumber"); + + b.ToTable("Suppliers", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportFont", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("FontData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("FontFamily") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FontStyle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FontFamily"); + + b.ToTable("ReportFonts"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("ImageData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.ToTable("ReportImages"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Orientation") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PageSize") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TemplateJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Thumbnail") + .HasColumnType("BLOB"); + + b.Property("ThumbnailMimeType") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.HasIndex("Nome"); + + b.ToTable("ReportTemplates"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cognome") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("TipoRisorsaId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TipoRisorsaId"); + + b.ToTable("Risorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("SalesOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SalesOrderId") + .HasColumnType("INTEGER"); + + b.Property("ShippedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SalesOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("SalesOrderLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TipoPastoId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TipoPastoId"); + + b.ToTable("TipiEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoMateriale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiMateriale"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoOspite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiOspite"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoPasto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiPasto"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoRisorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiRisorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Utente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cognome") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Nome") + .HasColumnType("TEXT"); + + b.Property("Ruolo") + .HasColumnType("TEXT"); + + b.Property("SolaLettura") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Utenti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.VirtualDataset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("VirtualDatasets"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBarcode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Barcode") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.ToTable("ArticleBarcodes", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Certifications") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CurrentQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("ExpiryDate") + .HasColumnType("TEXT"); + + b.Property("InitialQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("LastQualityCheckDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ProductionDate") + .HasColumnType("TEXT"); + + b.Property("QualityStatus") + .HasColumnType("INTEGER"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierBatch") + .HasColumnType("TEXT"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("Status"); + + b.HasIndex("ArticleId", "BatchNumber") + .IsUnique(); + + b.ToTable("ArticleBatches", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Attributes") + .HasColumnType("TEXT"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CurrentWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("ManufacturerSerial") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ProductionDate") + .HasColumnType("TEXT"); + + b.Property("SalesReference") + .HasColumnType("TEXT"); + + b.Property("SerialNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SoldDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarrantyExpiryDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("CurrentWarehouseId"); + + b.HasIndex("Status"); + + b.HasIndex("ArticleId", "SerialNumber") + .IsUnique(); + + b.ToTable("ArticleSerials", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdjustmentMovementId") + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("ConfirmedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("InventoryDate") + .HasColumnType("TEXT"); + + b.Property("NegativeDifferenceValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PositiveDifferenceValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AdjustmentMovementId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("InventoryDate"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("InventoryCounts", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCountLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CountedAt") + .HasColumnType("TEXT"); + + b.Property("CountedBy") + .HasColumnType("TEXT"); + + b.Property("CountedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("InventoryCountId") + .HasColumnType("INTEGER"); + + b.Property("LocationCode") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("SecondCountBy") + .HasColumnType("TEXT"); + + b.Property("SecondCountQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TheoreticalQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("BatchId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("InventoryCountId", "ArticleId", "WarehouseId", "BatchId") + .IsUnique(); + + b.ToTable("InventoryCountLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.MovementReason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsSystem") + .HasColumnType("INTEGER"); + + b.Property("MovementType") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("RequiresExternalReference") + .HasColumnType("INTEGER"); + + b.Property("RequiresValuation") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("StockSign") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("UpdatesAverageCost") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("MovementType"); + + b.ToTable("MovementReasons", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockLevel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("LastInventoryDate") + .HasColumnType("TEXT"); + + b.Property("LastMovementDate") + .HasColumnType("TEXT"); + + b.Property("LocationCode") + .HasColumnType("TEXT"); + + b.Property("OnOrderQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StockValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("LocationCode"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ArticleId", "WarehouseId", "BatchId") + .IsUnique(); + + b.ToTable("StockLevels", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("ConfirmedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("DocumentNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExternalDocumentType") + .HasColumnType("INTEGER"); + + b.Property("ExternalReference") + .HasColumnType("TEXT"); + + b.Property("MovementDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReasonId") + .HasColumnType("INTEGER"); + + b.Property("SourceWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("DocumentNumber") + .IsUnique(); + + b.HasIndex("ExternalReference"); + + b.HasIndex("MovementDate"); + + b.HasIndex("ReasonId"); + + b.HasIndex("SourceWarehouseId"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("StockMovements", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovementLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationLocationCode") + .HasColumnType("TEXT"); + + b.Property("ExternalLineReference") + .HasColumnType("TEXT"); + + b.Property("LineNumber") + .HasColumnType("INTEGER"); + + b.Property("LineValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("MovementId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SerialId") + .HasColumnType("INTEGER"); + + b.Property("SourceLocationCode") + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("MovementId", "LineNumber") + .IsUnique(); + + b.ToTable("StockMovementLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("ClosedBy") + .HasColumnType("TEXT"); + + b.Property("ClosedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("InboundQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("InboundValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("IsClosed") + .HasColumnType("INTEGER"); + + b.Property("Method") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OutboundQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("OutboundValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Period") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValuationDate") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsClosed"); + + b.HasIndex("ValuationDate"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("Period", "ArticleId", "WarehouseId") + .IsUnique(); + + b.ToTable("StockValuations", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuationLayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("IsExhausted") + .HasColumnType("INTEGER"); + + b.Property("LayerDate") + .HasColumnType("TEXT"); + + b.Property("OriginalQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("RemainingQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SourceMovementId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("IsExhausted"); + + b.HasIndex("SourceMovementId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ArticleId", "WarehouseId", "LayerDate"); + + b.ToTable("StockValuationLayers", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("Barcode") + .HasColumnType("TEXT"); + + b.Property("BaseSellingPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Depth") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiryWarningDays") + .HasColumnType("INTEGER"); + + b.Property("HasExpiry") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("TEXT"); + + b.Property("Image") + .HasColumnType("BLOB"); + + b.Property("ImageMimeType") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsBatchManaged") + .HasColumnType("INTEGER"); + + b.Property("IsSerialManaged") + .HasColumnType("INTEGER"); + + b.Property("LastPurchaseCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("LeadTimeDays") + .HasColumnType("INTEGER"); + + b.Property("ManufacturerCode") + .HasColumnType("TEXT"); + + b.Property("MaximumStock") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("MinimumStock") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReorderQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SecondaryUnitOfMeasure") + .HasColumnType("TEXT"); + + b.Property("ShortDescription") + .HasColumnType("TEXT"); + + b.Property("StandardCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StockManagement") + .HasColumnType("INTEGER"); + + b.Property("UnitConversionFactor") + .HasPrecision(18, 6) + .HasColumnType("TEXT"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValuationMethod") + .HasColumnType("INTEGER"); + + b.Property("Volume") + .HasPrecision(18, 6) + .HasColumnType("TEXT"); + + b.Property("Weight") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("WeightedAverageCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Barcode"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.ToTable("WarehouseArticles", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Color") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DefaultValuationMethod") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FullPath") + .HasColumnType("TEXT"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ParentCategoryId") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("FullPath"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("WarehouseArticleCategories", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("IsDefault"); + + b.ToTable("WarehouseLocations", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.HasOne("Apollinare.Domain.Entities.CodiceCategoria", "Categoria") + .WithMany("Articoli") + .HasForeignKey("CategoriaId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.TipoMateriale", "TipoMateriale") + .WithMany("Articoli") + .HasForeignKey("TipoMaterialeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Categoria"); + + b.Navigation("TipoMateriale"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.HasOne("Apollinare.Domain.Entities.Cliente", "Cliente") + .WithMany("Eventi") + .HasForeignKey("ClienteId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Location", "Location") + .WithMany("Eventi") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.TipoEvento", "TipoEvento") + .WithMany("Eventi") + .HasForeignKey("TipoEventoId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cliente"); + + b.Navigation("Location"); + + b.Navigation("TipoEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAcconto", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Acconti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAllegato", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Allegati") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAltroCosto", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("AltriCosti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDegustazione", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Degustazioni") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioOspiti", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliOspiti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.TipoOspite", "TipoOspite") + .WithMany("DettagliOspiti") + .HasForeignKey("TipoOspiteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + + b.Navigation("TipoOspite"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioPrelievo", b => + { + b.HasOne("Apollinare.Domain.Entities.Articolo", "Articolo") + .WithMany("DettagliPrelievo") + .HasForeignKey("ArticoloId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliPrelievo") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Articolo"); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioRisorsa", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliRisorse") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Risorsa", "Risorsa") + .WithMany("DettagliRisorse") + .HasForeignKey("RisorsaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + + b.Navigation("Risorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ModuleSubscription", b => + { + b.HasOne("Apollinare.Domain.Entities.AppModule", "Module") + .WithOne("Subscription") + .HasForeignKey("Apollinare.Domain.Entities.ModuleSubscription", "ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterialsComponent", b => + { + b.HasOne("Apollinare.Domain.Entities.Production.BillOfMaterials", "BillOfMaterials") + .WithMany("Components") + .HasForeignKey("BillOfMaterialsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "ComponentArticle") + .WithMany() + .HasForeignKey("ComponentArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BillOfMaterials"); + + b.Navigation("ComponentArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderComponent", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Production.ProductionOrder", "ProductionOrder") + .WithMany("Components") + .HasForeignKey("ProductionOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("ProductionOrder"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany() + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Purchases.Supplier", "Supplier") + .WithMany("PurchaseOrders") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Purchases.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PurchaseOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("WarehouseArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.HasOne("Apollinare.Domain.Entities.TipoRisorsa", "TipoRisorsa") + .WithMany("Risorse") + .HasForeignKey("TipoRisorsaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("TipoRisorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Cliente", "Customer") + .WithMany("SalesOrders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Sales.SalesOrder", "SalesOrder") + .WithMany("Lines") + .HasForeignKey("SalesOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesOrder"); + + b.Navigation("WarehouseArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.HasOne("Apollinare.Domain.Entities.TipoPasto", "TipoPasto") + .WithMany("TipiEvento") + .HasForeignKey("TipoPastoId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("TipoPasto"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBarcode", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Barcodes") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Batches") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Serials") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("Serials") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "CurrentWarehouse") + .WithMany() + .HasForeignKey("CurrentWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("CurrentWarehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "AdjustmentMovement") + .WithMany() + .HasForeignKey("AdjustmentMovementId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("AdjustmentMovement"); + + b.Navigation("Category"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCountLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.InventoryCount", "InventoryCount") + .WithMany("Lines") + .HasForeignKey("InventoryCountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("InventoryCount"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockLevel", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("StockLevels") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("StockLevels") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany("StockLevels") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany("DestinationMovements") + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.MovementReason", "Reason") + .WithMany("Movements") + .HasForeignKey("ReasonId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "SourceWarehouse") + .WithMany("SourceMovements") + .HasForeignKey("SourceWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Reason"); + + b.Navigation("SourceWarehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovementLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("MovementLines") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("MovementLines") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "Movement") + .WithMany("Lines") + .HasForeignKey("MovementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleSerial", "Serial") + .WithMany("MovementLines") + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("Movement"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuation", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuationLayer", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "SourceMovement") + .WithMany() + .HasForeignKey("SourceMovementId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("SourceMovement"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "Category") + .WithMany("Articles") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.AppModule", b => + { + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.Navigation("DettagliPrelievo"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => + { + b.Navigation("Eventi"); + + b.Navigation("SalesOrders"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => + { + b.Navigation("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.Navigation("Acconti"); + + b.Navigation("Allegati"); + + b.Navigation("AltriCosti"); + + b.Navigation("Degustazioni"); + + b.Navigation("DettagliOspiti"); + + b.Navigation("DettagliPrelievo"); + + b.Navigation("DettagliRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Location", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Navigation("PurchaseOrders"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.Navigation("DettagliRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoMateriale", b => + { + b.Navigation("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoOspite", b => + { + b.Navigation("DettagliOspiti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoPasto", b => + { + b.Navigation("TipiEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoRisorsa", b => + { + b.Navigation("Risorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.Navigation("MovementLines"); + + b.Navigation("Serials"); + + b.Navigation("StockLevels"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.Navigation("MovementLines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.MovementReason", b => + { + b.Navigation("Movements"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.Navigation("Barcodes"); + + b.Navigation("Batches"); + + b.Navigation("MovementLines"); + + b.Navigation("Serials"); + + b.Navigation("StockLevels"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.Navigation("Articles"); + + b.Navigation("ChildCategories"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", b => + { + b.Navigation("DestinationMovements"); + + b.Navigation("SourceMovements"); + + b.Navigation("StockLevels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/20251130152222_AddProductionModule.cs b/src/Apollinare.Infrastructure/Migrations/20251130152222_AddProductionModule.cs new file mode 100644 index 0000000..c9e4b7d --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251130152222_AddProductionModule.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + /// + public partial class AddProductionModule : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BillOfMaterials", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", nullable: false), + ArticleId = table.Column(type: "INTEGER", nullable: false), + Quantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BillOfMaterials", x => x.Id); + table.ForeignKey( + name: "FK_BillOfMaterials_WarehouseArticles_ArticleId", + column: x => x.ArticleId, + principalTable: "WarehouseArticles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ProductionOrders", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Code = table.Column(type: "TEXT", nullable: false), + ArticleId = table.Column(type: "INTEGER", nullable: false), + Quantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + StartDate = table.Column(type: "TEXT", nullable: false), + EndDate = table.Column(type: "TEXT", nullable: true), + DueDate = table.Column(type: "TEXT", nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + Notes = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductionOrders", x => x.Id); + table.ForeignKey( + name: "FK_ProductionOrders_WarehouseArticles_ArticleId", + column: x => x.ArticleId, + principalTable: "WarehouseArticles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "BillOfMaterialsComponents", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + BillOfMaterialsId = table.Column(type: "INTEGER", nullable: false), + ComponentArticleId = table.Column(type: "INTEGER", nullable: false), + Quantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + ScrapPercentage = table.Column(type: "TEXT", precision: 18, scale: 2, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BillOfMaterialsComponents", x => x.Id); + table.ForeignKey( + name: "FK_BillOfMaterialsComponents_BillOfMaterials_BillOfMaterialsId", + column: x => x.BillOfMaterialsId, + principalTable: "BillOfMaterials", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BillOfMaterialsComponents_WarehouseArticles_ComponentArticleId", + column: x => x.ComponentArticleId, + principalTable: "WarehouseArticles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ProductionOrderComponents", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ProductionOrderId = table.Column(type: "INTEGER", nullable: false), + ArticleId = table.Column(type: "INTEGER", nullable: false), + RequiredQuantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + ConsumedQuantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductionOrderComponents", x => x.Id); + table.ForeignKey( + name: "FK_ProductionOrderComponents_ProductionOrders_ProductionOrderId", + column: x => x.ProductionOrderId, + principalTable: "ProductionOrders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProductionOrderComponents_WarehouseArticles_ArticleId", + column: x => x.ArticleId, + principalTable: "WarehouseArticles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BillOfMaterials_ArticleId", + table: "BillOfMaterials", + column: "ArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_BillOfMaterials_IsActive", + table: "BillOfMaterials", + column: "IsActive"); + + migrationBuilder.CreateIndex( + name: "IX_BillOfMaterialsComponents_BillOfMaterialsId", + table: "BillOfMaterialsComponents", + column: "BillOfMaterialsId"); + + migrationBuilder.CreateIndex( + name: "IX_BillOfMaterialsComponents_ComponentArticleId", + table: "BillOfMaterialsComponents", + column: "ComponentArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrderComponents_ArticleId", + table: "ProductionOrderComponents", + column: "ArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrderComponents_ProductionOrderId", + table: "ProductionOrderComponents", + column: "ProductionOrderId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrders_ArticleId", + table: "ProductionOrders", + column: "ArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrders_Code", + table: "ProductionOrders", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrders_StartDate", + table: "ProductionOrders", + column: "StartDate"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrders_Status", + table: "ProductionOrders", + column: "Status"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BillOfMaterialsComponents"); + + migrationBuilder.DropTable( + name: "ProductionOrderComponents"); + + migrationBuilder.DropTable( + name: "BillOfMaterials"); + + migrationBuilder.DropTable( + name: "ProductionOrders"); + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/20251130161658_AddAdvancedProduction.Designer.cs b/src/Apollinare.Infrastructure/Migrations/20251130161658_AddAdvancedProduction.Designer.cs new file mode 100644 index 0000000..5c38e7a --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251130161658_AddAdvancedProduction.Designer.cs @@ -0,0 +1,4341 @@ +// +using System; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + [DbContext(typeof(AppollinareDbContext))] + [Migration("20251130161658_AddAdvancedProduction")] + partial class AddAdvancedProduction + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); + + modelBuilder.Entity("Apollinare.Domain.Entities.AppModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BasePrice") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Dependencies") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("IsAvailable") + .HasColumnType("INTEGER"); + + b.Property("IsCore") + .HasColumnType("INTEGER"); + + b.Property("MonthlyMultiplier") + .HasPrecision(5, 2) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RoutePath") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("SortOrder"); + + b.ToTable("AppModules"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("CategoriaId") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Immagine") + .HasColumnType("BLOB"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("QtaDisponibile") + .HasColumnType("TEXT"); + + b.Property("QtaStdA") + .HasColumnType("TEXT"); + + b.Property("QtaStdB") + .HasColumnType("TEXT"); + + b.Property("QtaStdS") + .HasColumnType("TEXT"); + + b.Property("TipoMaterialeId") + .HasColumnType("INTEGER"); + + b.Property("UnitaMisura") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CategoriaId"); + + b.HasIndex("Codice") + .IsUnique(); + + b.HasIndex("TipoMaterialeId"); + + b.ToTable("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.AutoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EntityCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EntityName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsReadOnly") + .HasColumnType("INTEGER"); + + b.Property("LastResetMonth") + .HasColumnType("INTEGER"); + + b.Property("LastResetYear") + .HasColumnType("INTEGER"); + + b.Property("LastSequence") + .HasColumnType("INTEGER"); + + b.Property("ModuleCode") + .HasColumnType("TEXT"); + + b.Property("Pattern") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Prefix") + .HasColumnType("TEXT"); + + b.Property("ResetSequenceMonthly") + .HasColumnType("INTEGER"); + + b.Property("ResetSequenceYearly") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntityCode") + .IsUnique(); + + b.HasIndex("ModuleCode"); + + b.ToTable("AutoCodes"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cap") + .HasColumnType("TEXT"); + + b.Property("Citta") + .HasColumnType("TEXT"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + + b.Property("CodiceDestinatario") + .HasColumnType("TEXT"); + + b.Property("CodiceFiscale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Indirizzo") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("PartitaIva") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Provincia") + .HasColumnType("TEXT"); + + b.Property("RagioneSociale") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartitaIva"); + + b.HasIndex("RagioneSociale"); + + b.ToTable("Clienti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CoeffA") + .HasColumnType("TEXT"); + + b.Property("CoeffB") + .HasColumnType("TEXT"); + + b.Property("CoeffS") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CodiciCategoria"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Configurazione", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Chiave") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Valore") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Chiave") + .IsUnique(); + + b.ToTable("Configurazioni"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CustomFieldDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DefaultValue") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EntityName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FieldName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsRequired") + .HasColumnType("INTEGER"); + + b.Property("Label") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OptionsJson") + .HasColumnType("TEXT"); + + b.Property("Placeholder") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValidationRegex") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EntityName"); + + b.HasIndex("EntityName", "FieldName") + .IsUnique(); + + b.ToTable("CustomFieldDefinitions"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClienteId") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .HasColumnType("TEXT"); + + b.Property("Confermato") + .HasColumnType("INTEGER"); + + b.Property("CostoPersona") + .HasColumnType("TEXT"); + + b.Property("CostoTotale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataEvento") + .HasColumnType("TEXT"); + + b.Property("DataScadenzaPreventivo") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("NoteAllestimento") + .HasColumnType("TEXT"); + + b.Property("NoteCliente") + .HasColumnType("TEXT"); + + b.Property("NoteCucina") + .HasColumnType("TEXT"); + + b.Property("NoteInterne") + .HasColumnType("TEXT"); + + b.Property("NumeroOspiti") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiAdulti") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiBambini") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiBuffet") + .HasColumnType("INTEGER"); + + b.Property("NumeroOspitiSeduti") + .HasColumnType("INTEGER"); + + b.Property("OraFine") + .HasColumnType("TEXT"); + + b.Property("OraInizio") + .HasColumnType("TEXT"); + + b.Property("Saldo") + .HasColumnType("TEXT"); + + b.Property("Stato") + .HasColumnType("INTEGER"); + + b.Property("TipoEventoId") + .HasColumnType("INTEGER"); + + b.Property("TotaleAcconti") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClienteId"); + + b.HasIndex("Codice"); + + b.HasIndex("DataEvento"); + + b.HasIndex("LocationId"); + + b.HasIndex("Stato"); + + b.HasIndex("TipoEventoId"); + + b.ToTable("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAcconto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AConferma") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataPagamento") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Importo") + .HasColumnType("TEXT"); + + b.Property("MetodoPagamento") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAcconti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAllegato", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Contenuto") + .HasColumnType("BLOB"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("NomeFile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAllegati"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAltroCosto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AliquotaIva") + .HasColumnType("TEXT"); + + b.Property("ApplicaIva") + .HasColumnType("INTEGER"); + + b.Property("CostoUnitario") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("Quantita") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiAltriCosti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDegustazione", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completata") + .HasColumnType("INTEGER"); + + b.Property("CostoDegustazione") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DataDegustazione") + .HasColumnType("TEXT"); + + b.Property("Detraibile") + .HasColumnType("INTEGER"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Luogo") + .HasColumnType("TEXT"); + + b.Property("Menu") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("NumeroPaganti") + .HasColumnType("INTEGER"); + + b.Property("NumeroPersone") + .HasColumnType("INTEGER"); + + b.Property("Ora") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiDegustazioni"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioOspiti", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CostoUnitario") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Numero") + .HasColumnType("INTEGER"); + + b.Property("Ordine") + .HasColumnType("INTEGER"); + + b.Property("Sconto") + .HasColumnType("TEXT"); + + b.Property("TipoOspiteId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.HasIndex("TipoOspiteId"); + + b.ToTable("EventiDettaglioOspiti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioPrelievo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticoloId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("QtaCalcolata") + .HasColumnType("TEXT"); + + b.Property("QtaEffettiva") + .HasColumnType("TEXT"); + + b.Property("QtaRichiesta") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticoloId"); + + b.HasIndex("EventoId"); + + b.ToTable("EventiDettaglioPrelievo"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioRisorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Costo") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EventoId") + .HasColumnType("INTEGER"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OraFine") + .HasColumnType("TEXT"); + + b.Property("OraInizio") + .HasColumnType("TEXT"); + + b.Property("OreLavoro") + .HasColumnType("TEXT"); + + b.Property("RisorsaId") + .HasColumnType("INTEGER"); + + b.Property("Ruolo") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EventoId"); + + b.HasIndex("RisorsaId"); + + b.ToTable("EventiDettaglioRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cap") + .HasColumnType("TEXT"); + + b.Property("Citta") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DistanzaKm") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Indirizzo") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Provincia") + .HasColumnType("TEXT"); + + b.Property("Referente") + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Nome"); + + b.ToTable("Location"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ModuleSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoRenew") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastRenewalDate") + .HasColumnType("TEXT"); + + b.Property("ModuleId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaidPrice") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("SubscriptionType") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ModuleId") + .IsUnique(); + + b.ToTable("ModuleSubscriptions"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsActive"); + + b.ToTable("BillOfMaterials", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterialsComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BillOfMaterialsId") + .HasColumnType("INTEGER"); + + b.Property("ComponentArticleId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ScrapPercentage") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BillOfMaterialsId"); + + b.HasIndex("ComponentArticleId"); + + b.ToTable("BillOfMaterialsComponents", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.MrpSuggestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("CalculationDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("IsProcessed") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SuggestionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("CalculationDate"); + + b.HasIndex("IsProcessed"); + + b.ToTable("MrpSuggestions", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsActive"); + + b.ToTable("ProductionCycles", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCyclePhase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DurationPerUnitMinutes") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProductionCycleId") + .HasColumnType("INTEGER"); + + b.Property("Sequence") + .HasColumnType("INTEGER"); + + b.Property("SetupTimeMinutes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WorkCenterId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProductionCycleId"); + + b.HasIndex("WorkCenterId"); + + b.ToTable("ProductionCyclePhases", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DueDate") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("StartDate"); + + b.HasIndex("Status"); + + b.ToTable("ProductionOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("ConsumedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("ProductionOrderId") + .HasColumnType("INTEGER"); + + b.Property("RequiredQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("ProductionOrderId"); + + b.ToTable("ProductionOrderComponents", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderPhase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActualDurationMinutes") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("EstimatedDurationMinutes") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProductionOrderId") + .HasColumnType("INTEGER"); + + b.Property("QuantityCompleted") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("QuantityScrapped") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Sequence") + .HasColumnType("INTEGER"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WorkCenterId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProductionOrderId"); + + b.HasIndex("Status"); + + b.HasIndex("WorkCenterId"); + + b.ToTable("ProductionOrderPhases", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.WorkCenter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CostPerHour") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.ToTable("WorkCenters", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SupplierId"); + + b.ToTable("PurchaseOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("PurchaseOrderId") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReceivedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("PurchaseOrderLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("FiscalCode") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaymentTerms") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("VatNumber") + .HasColumnType("TEXT"); + + b.Property("Website") + .HasColumnType("TEXT"); + + b.Property("ZipCode") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("Name"); + + b.HasIndex("VatNumber"); + + b.ToTable("Suppliers", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportFont", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("FontData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("FontFamily") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FontStyle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FontFamily"); + + b.ToTable("ReportFonts"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("ImageData") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.ToTable("ReportImages"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ReportTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Orientation") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PageSize") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TemplateJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Thumbnail") + .HasColumnType("BLOB"); + + b.Property("ThumbnailMimeType") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.HasIndex("Nome"); + + b.ToTable("ReportTemplates"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cognome") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Telefono") + .HasColumnType("TEXT"); + + b.Property("TipoRisorsaId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TipoRisorsaId"); + + b.ToTable("Risorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("SalesOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SalesOrderId") + .HasColumnType("INTEGER"); + + b.Property("ShippedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SalesOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("SalesOrderLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TipoPastoId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TipoPastoId"); + + b.ToTable("TipiEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoMateriale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiMateriale"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoOspite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiOspite"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoPasto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiPasto"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoRisorsa", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TipiRisorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Utente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Cognome") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Nome") + .HasColumnType("TEXT"); + + b.Property("Ruolo") + .HasColumnType("TEXT"); + + b.Property("SolaLettura") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Utenti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.VirtualDataset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attivo") + .HasColumnType("INTEGER"); + + b.Property("Categoria") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Descrizione") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Categoria"); + + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("VirtualDatasets"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBarcode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Barcode") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.ToTable("ArticleBarcodes", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Certifications") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CurrentQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("ExpiryDate") + .HasColumnType("TEXT"); + + b.Property("InitialQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("LastQualityCheckDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ProductionDate") + .HasColumnType("TEXT"); + + b.Property("QualityStatus") + .HasColumnType("INTEGER"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierBatch") + .HasColumnType("TEXT"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("Status"); + + b.HasIndex("ArticleId", "BatchNumber") + .IsUnique(); + + b.ToTable("ArticleBatches", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Attributes") + .HasColumnType("TEXT"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CurrentWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("ManufacturerSerial") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ProductionDate") + .HasColumnType("TEXT"); + + b.Property("SalesReference") + .HasColumnType("TEXT"); + + b.Property("SerialNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SoldDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarrantyExpiryDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("CurrentWarehouseId"); + + b.HasIndex("Status"); + + b.HasIndex("ArticleId", "SerialNumber") + .IsUnique(); + + b.ToTable("ArticleSerials", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdjustmentMovementId") + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("ConfirmedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("InventoryDate") + .HasColumnType("TEXT"); + + b.Property("NegativeDifferenceValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PositiveDifferenceValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AdjustmentMovementId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("InventoryDate"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("InventoryCounts", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCountLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CountedAt") + .HasColumnType("TEXT"); + + b.Property("CountedBy") + .HasColumnType("TEXT"); + + b.Property("CountedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("InventoryCountId") + .HasColumnType("INTEGER"); + + b.Property("LocationCode") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("SecondCountBy") + .HasColumnType("TEXT"); + + b.Property("SecondCountQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TheoreticalQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("BatchId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("InventoryCountId", "ArticleId", "WarehouseId", "BatchId") + .IsUnique(); + + b.ToTable("InventoryCountLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.MovementReason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsSystem") + .HasColumnType("INTEGER"); + + b.Property("MovementType") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("RequiresExternalReference") + .HasColumnType("INTEGER"); + + b.Property("RequiresValuation") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("StockSign") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("UpdatesAverageCost") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("MovementType"); + + b.ToTable("MovementReasons", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockLevel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("LastInventoryDate") + .HasColumnType("TEXT"); + + b.Property("LastMovementDate") + .HasColumnType("TEXT"); + + b.Property("LocationCode") + .HasColumnType("TEXT"); + + b.Property("OnOrderQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StockValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("LocationCode"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ArticleId", "WarehouseId", "BatchId") + .IsUnique(); + + b.ToTable("StockLevels", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("ConfirmedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("DocumentNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExternalDocumentType") + .HasColumnType("INTEGER"); + + b.Property("ExternalReference") + .HasColumnType("TEXT"); + + b.Property("MovementDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReasonId") + .HasColumnType("INTEGER"); + + b.Property("SourceWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("DocumentNumber") + .IsUnique(); + + b.HasIndex("ExternalReference"); + + b.HasIndex("MovementDate"); + + b.HasIndex("ReasonId"); + + b.HasIndex("SourceWarehouseId"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("StockMovements", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovementLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationLocationCode") + .HasColumnType("TEXT"); + + b.Property("ExternalLineReference") + .HasColumnType("TEXT"); + + b.Property("LineNumber") + .HasColumnType("INTEGER"); + + b.Property("LineValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("MovementId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SerialId") + .HasColumnType("INTEGER"); + + b.Property("SourceLocationCode") + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("MovementId", "LineNumber") + .IsUnique(); + + b.ToTable("StockMovementLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("ClosedBy") + .HasColumnType("TEXT"); + + b.Property("ClosedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("InboundQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("InboundValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("IsClosed") + .HasColumnType("INTEGER"); + + b.Property("Method") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OutboundQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("OutboundValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Period") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalValue") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValuationDate") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsClosed"); + + b.HasIndex("ValuationDate"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("Period", "ArticleId", "WarehouseId") + .IsUnique(); + + b.ToTable("StockValuations", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuationLayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("BatchId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("IsExhausted") + .HasColumnType("INTEGER"); + + b.Property("LayerDate") + .HasColumnType("TEXT"); + + b.Property("OriginalQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("RemainingQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SourceMovementId") + .HasColumnType("INTEGER"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BatchId"); + + b.HasIndex("IsExhausted"); + + b.HasIndex("SourceMovementId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ArticleId", "WarehouseId", "LayerDate"); + + b.ToTable("StockValuationLayers", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("Barcode") + .HasColumnType("TEXT"); + + b.Property("BaseSellingPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Depth") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiryWarningDays") + .HasColumnType("INTEGER"); + + b.Property("HasExpiry") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("TEXT"); + + b.Property("Image") + .HasColumnType("BLOB"); + + b.Property("ImageMimeType") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsBatchManaged") + .HasColumnType("INTEGER"); + + b.Property("IsSerialManaged") + .HasColumnType("INTEGER"); + + b.Property("LastPurchaseCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("LeadTimeDays") + .HasColumnType("INTEGER"); + + b.Property("ManufacturerCode") + .HasColumnType("TEXT"); + + b.Property("MaximumStock") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("MinimumStock") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReorderQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SecondaryUnitOfMeasure") + .HasColumnType("TEXT"); + + b.Property("ShortDescription") + .HasColumnType("TEXT"); + + b.Property("StandardCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StockManagement") + .HasColumnType("INTEGER"); + + b.Property("UnitConversionFactor") + .HasPrecision(18, 6) + .HasColumnType("TEXT"); + + b.Property("UnitOfMeasure") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("ValuationMethod") + .HasColumnType("INTEGER"); + + b.Property("Volume") + .HasPrecision(18, 6) + .HasColumnType("TEXT"); + + b.Property("Weight") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("WeightedAverageCost") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Barcode"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.ToTable("WarehouseArticles", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Color") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DefaultValuationMethod") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FullPath") + .HasColumnType("TEXT"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ParentCategoryId") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("FullPath"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("WarehouseArticleCategories", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("IsDefault"); + + b.ToTable("WarehouseLocations", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.HasOne("Apollinare.Domain.Entities.CodiceCategoria", "Categoria") + .WithMany("Articoli") + .HasForeignKey("CategoriaId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.TipoMateriale", "TipoMateriale") + .WithMany("Articoli") + .HasForeignKey("TipoMaterialeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Categoria"); + + b.Navigation("TipoMateriale"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.HasOne("Apollinare.Domain.Entities.Cliente", "Cliente") + .WithMany("Eventi") + .HasForeignKey("ClienteId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Location", "Location") + .WithMany("Eventi") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.TipoEvento", "TipoEvento") + .WithMany("Eventi") + .HasForeignKey("TipoEventoId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Cliente"); + + b.Navigation("Location"); + + b.Navigation("TipoEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAcconto", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Acconti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAllegato", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Allegati") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoAltroCosto", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("AltriCosti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDegustazione", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("Degustazioni") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioOspiti", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliOspiti") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.TipoOspite", "TipoOspite") + .WithMany("DettagliOspiti") + .HasForeignKey("TipoOspiteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + + b.Navigation("TipoOspite"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioPrelievo", b => + { + b.HasOne("Apollinare.Domain.Entities.Articolo", "Articolo") + .WithMany("DettagliPrelievo") + .HasForeignKey("ArticoloId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliPrelievo") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Articolo"); + + b.Navigation("Evento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.EventoDettaglioRisorsa", b => + { + b.HasOne("Apollinare.Domain.Entities.Evento", "Evento") + .WithMany("DettagliRisorse") + .HasForeignKey("EventoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Risorsa", "Risorsa") + .WithMany("DettagliRisorse") + .HasForeignKey("RisorsaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Evento"); + + b.Navigation("Risorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.ModuleSubscription", b => + { + b.HasOne("Apollinare.Domain.Entities.AppModule", "Module") + .WithOne("Subscription") + .HasForeignKey("Apollinare.Domain.Entities.ModuleSubscription", "ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterialsComponent", b => + { + b.HasOne("Apollinare.Domain.Entities.Production.BillOfMaterials", "BillOfMaterials") + .WithMany("Components") + .HasForeignKey("BillOfMaterialsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "ComponentArticle") + .WithMany() + .HasForeignKey("ComponentArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BillOfMaterials"); + + b.Navigation("ComponentArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.MrpSuggestion", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCycle", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCyclePhase", b => + { + b.HasOne("Apollinare.Domain.Entities.Production.ProductionCycle", "ProductionCycle") + .WithMany("Phases") + .HasForeignKey("ProductionCycleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Production.WorkCenter", "WorkCenter") + .WithMany() + .HasForeignKey("WorkCenterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ProductionCycle"); + + b.Navigation("WorkCenter"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderComponent", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Production.ProductionOrder", "ProductionOrder") + .WithMany("Components") + .HasForeignKey("ProductionOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("ProductionOrder"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderPhase", b => + { + b.HasOne("Apollinare.Domain.Entities.Production.ProductionOrder", "ProductionOrder") + .WithMany("Phases") + .HasForeignKey("ProductionOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Production.WorkCenter", "WorkCenter") + .WithMany() + .HasForeignKey("WorkCenterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ProductionOrder"); + + b.Navigation("WorkCenter"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany() + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Purchases.Supplier", "Supplier") + .WithMany("PurchaseOrders") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Purchases.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PurchaseOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("WarehouseArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.HasOne("Apollinare.Domain.Entities.TipoRisorsa", "TipoRisorsa") + .WithMany("Risorse") + .HasForeignKey("TipoRisorsaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("TipoRisorsa"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Cliente", "Customer") + .WithMany("SalesOrders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Sales.SalesOrder", "SalesOrder") + .WithMany("Lines") + .HasForeignKey("SalesOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesOrder"); + + b.Navigation("WarehouseArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.HasOne("Apollinare.Domain.Entities.TipoPasto", "TipoPasto") + .WithMany("TipiEvento") + .HasForeignKey("TipoPastoId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("TipoPasto"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBarcode", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Barcodes") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Batches") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("Serials") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("Serials") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "CurrentWarehouse") + .WithMany() + .HasForeignKey("CurrentWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("CurrentWarehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "AdjustmentMovement") + .WithMany() + .HasForeignKey("AdjustmentMovementId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("AdjustmentMovement"); + + b.Navigation("Category"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCountLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.InventoryCount", "InventoryCount") + .WithMany("Lines") + .HasForeignKey("InventoryCountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("InventoryCount"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockLevel", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("StockLevels") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("StockLevels") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany("StockLevels") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany("DestinationMovements") + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.MovementReason", "Reason") + .WithMany("Movements") + .HasForeignKey("ReasonId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "SourceWarehouse") + .WithMany("SourceMovements") + .HasForeignKey("SourceWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Reason"); + + b.Navigation("SourceWarehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovementLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany("MovementLines") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany("MovementLines") + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "Movement") + .WithMany("Lines") + .HasForeignKey("MovementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleSerial", "Serial") + .WithMany("MovementLines") + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("Movement"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuation", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Article"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockValuationLayer", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.ArticleBatch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.StockMovement", "SourceMovement") + .WithMany() + .HasForeignKey("SourceMovementId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Batch"); + + b.Navigation("SourceMovement"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "Category") + .WithMany("Articles") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.AppModule", b => + { + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Articolo", b => + { + b.Navigation("DettagliPrelievo"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => + { + b.Navigation("Eventi"); + + b.Navigation("SalesOrders"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => + { + b.Navigation("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Evento", b => + { + b.Navigation("Acconti"); + + b.Navigation("Allegati"); + + b.Navigation("AltriCosti"); + + b.Navigation("Degustazioni"); + + b.Navigation("DettagliOspiti"); + + b.Navigation("DettagliPrelievo"); + + b.Navigation("DettagliRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Location", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCycle", b => + { + b.Navigation("Phases"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.Navigation("Components"); + + b.Navigation("Phases"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Navigation("PurchaseOrders"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => + { + b.Navigation("DettagliRisorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => + { + b.Navigation("Eventi"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoMateriale", b => + { + b.Navigation("Articoli"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoOspite", b => + { + b.Navigation("DettagliOspiti"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoPasto", b => + { + b.Navigation("TipiEvento"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.TipoRisorsa", b => + { + b.Navigation("Risorse"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleBatch", b => + { + b.Navigation("MovementLines"); + + b.Navigation("Serials"); + + b.Navigation("StockLevels"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.ArticleSerial", b => + { + b.Navigation("MovementLines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.InventoryCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.MovementReason", b => + { + b.Navigation("Movements"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.StockMovement", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", b => + { + b.Navigation("Barcodes"); + + b.Navigation("Batches"); + + b.Navigation("MovementLines"); + + b.Navigation("Serials"); + + b.Navigation("StockLevels"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseArticleCategory", b => + { + b.Navigation("Articles"); + + b.Navigation("ChildCategories"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", b => + { + b.Navigation("DestinationMovements"); + + b.Navigation("SourceMovements"); + + b.Navigation("StockLevels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/20251130161658_AddAdvancedProduction.cs b/src/Apollinare.Infrastructure/Migrations/20251130161658_AddAdvancedProduction.cs new file mode 100644 index 0000000..c1d2fc0 --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251130161658_AddAdvancedProduction.cs @@ -0,0 +1,251 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + /// + public partial class AddAdvancedProduction : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MrpSuggestions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CalculationDate = table.Column(type: "TEXT", nullable: false), + ArticleId = table.Column(type: "INTEGER", nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + Quantity = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + SuggestionDate = table.Column(type: "TEXT", nullable: false), + Reason = table.Column(type: "TEXT", nullable: false), + IsProcessed = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MrpSuggestions", x => x.Id); + table.ForeignKey( + name: "FK_MrpSuggestions_WarehouseArticles_ArticleId", + column: x => x.ArticleId, + principalTable: "WarehouseArticles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ProductionCycles", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", nullable: true), + ArticleId = table.Column(type: "INTEGER", nullable: false), + IsDefault = table.Column(type: "INTEGER", nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductionCycles", x => x.Id); + table.ForeignKey( + name: "FK_ProductionCycles_WarehouseArticles_ArticleId", + column: x => x.ArticleId, + principalTable: "WarehouseArticles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "WorkCenters", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Code = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", nullable: true), + CostPerHour = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_WorkCenters", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ProductionCyclePhases", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ProductionCycleId = table.Column(type: "INTEGER", nullable: false), + Sequence = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", nullable: true), + WorkCenterId = table.Column(type: "INTEGER", nullable: false), + DurationPerUnitMinutes = table.Column(type: "INTEGER", nullable: false), + SetupTimeMinutes = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductionCyclePhases", x => x.Id); + table.ForeignKey( + name: "FK_ProductionCyclePhases_ProductionCycles_ProductionCycleId", + column: x => x.ProductionCycleId, + principalTable: "ProductionCycles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProductionCyclePhases_WorkCenters_WorkCenterId", + column: x => x.WorkCenterId, + principalTable: "WorkCenters", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ProductionOrderPhases", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ProductionOrderId = table.Column(type: "INTEGER", nullable: false), + Sequence = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + WorkCenterId = table.Column(type: "INTEGER", nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + StartDate = table.Column(type: "TEXT", nullable: true), + EndDate = table.Column(type: "TEXT", nullable: true), + QuantityCompleted = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + QuantityScrapped = table.Column(type: "TEXT", precision: 18, scale: 4, nullable: false), + EstimatedDurationMinutes = table.Column(type: "INTEGER", nullable: false), + ActualDurationMinutes = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: true), + CreatedBy = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + UpdatedBy = table.Column(type: "TEXT", nullable: true), + CustomFieldsJson = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductionOrderPhases", x => x.Id); + table.ForeignKey( + name: "FK_ProductionOrderPhases_ProductionOrders_ProductionOrderId", + column: x => x.ProductionOrderId, + principalTable: "ProductionOrders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProductionOrderPhases_WorkCenters_WorkCenterId", + column: x => x.WorkCenterId, + principalTable: "WorkCenters", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_MrpSuggestions_ArticleId", + table: "MrpSuggestions", + column: "ArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_MrpSuggestions_CalculationDate", + table: "MrpSuggestions", + column: "CalculationDate"); + + migrationBuilder.CreateIndex( + name: "IX_MrpSuggestions_IsProcessed", + table: "MrpSuggestions", + column: "IsProcessed"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionCyclePhases_ProductionCycleId", + table: "ProductionCyclePhases", + column: "ProductionCycleId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionCyclePhases_WorkCenterId", + table: "ProductionCyclePhases", + column: "WorkCenterId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionCycles_ArticleId", + table: "ProductionCycles", + column: "ArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionCycles_IsActive", + table: "ProductionCycles", + column: "IsActive"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrderPhases_ProductionOrderId", + table: "ProductionOrderPhases", + column: "ProductionOrderId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrderPhases_Status", + table: "ProductionOrderPhases", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_ProductionOrderPhases_WorkCenterId", + table: "ProductionOrderPhases", + column: "WorkCenterId"); + + migrationBuilder.CreateIndex( + name: "IX_WorkCenters_Code", + table: "WorkCenters", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_WorkCenters_IsActive", + table: "WorkCenters", + column: "IsActive"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MrpSuggestions"); + + migrationBuilder.DropTable( + name: "ProductionCyclePhases"); + + migrationBuilder.DropTable( + name: "ProductionOrderPhases"); + + migrationBuilder.DropTable( + name: "ProductionCycles"); + + migrationBuilder.DropTable( + name: "WorkCenters"); + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/AppollinareDbContextModelSnapshot.cs b/src/Apollinare.Infrastructure/Migrations/AppollinareDbContextModelSnapshot.cs index a4eac8c..d8690a2 100644 --- a/src/Apollinare.Infrastructure/Migrations/AppollinareDbContextModelSnapshot.cs +++ b/src/Apollinare.Infrastructure/Migrations/AppollinareDbContextModelSnapshot.cs @@ -1059,6 +1059,693 @@ namespace Apollinare.Infrastructure.Migrations b.ToTable("ModuleSubscriptions"); }); + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsActive"); + + b.ToTable("BillOfMaterials", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterialsComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BillOfMaterialsId") + .HasColumnType("INTEGER"); + + b.Property("ComponentArticleId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ScrapPercentage") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BillOfMaterialsId"); + + b.HasIndex("ComponentArticleId"); + + b.ToTable("BillOfMaterialsComponents", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.MrpSuggestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("CalculationDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("IsProcessed") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SuggestionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("CalculationDate"); + + b.HasIndex("IsProcessed"); + + b.ToTable("MrpSuggestions", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("IsActive"); + + b.ToTable("ProductionCycles", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCyclePhase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DurationPerUnitMinutes") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProductionCycleId") + .HasColumnType("INTEGER"); + + b.Property("Sequence") + .HasColumnType("INTEGER"); + + b.Property("SetupTimeMinutes") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WorkCenterId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProductionCycleId"); + + b.HasIndex("WorkCenterId"); + + b.ToTable("ProductionCyclePhases", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DueDate") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("StartDate"); + + b.HasIndex("Status"); + + b.ToTable("ProductionOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArticleId") + .HasColumnType("INTEGER"); + + b.Property("ConsumedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("ProductionOrderId") + .HasColumnType("INTEGER"); + + b.Property("RequiredQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("ProductionOrderId"); + + b.ToTable("ProductionOrderComponents", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderPhase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActualDurationMinutes") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("EstimatedDurationMinutes") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProductionOrderId") + .HasColumnType("INTEGER"); + + b.Property("QuantityCompleted") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("QuantityScrapped") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Sequence") + .HasColumnType("INTEGER"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WorkCenterId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProductionOrderId"); + + b.HasIndex("Status"); + + b.HasIndex("WorkCenterId"); + + b.ToTable("ProductionOrderPhases", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.WorkCenter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CostPerHour") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.ToTable("WorkCenters", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("DestinationWarehouseId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("SupplierId") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestinationWarehouseId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SupplierId"); + + b.ToTable("PurchaseOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("PurchaseOrderId") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("ReceivedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("PurchaseOrderLines", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnType("TEXT"); + + b.Property("City") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("FiscalCode") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("PaymentTerms") + .HasColumnType("TEXT"); + + b.Property("Pec") + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasColumnType("TEXT"); + + b.Property("Province") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("VatNumber") + .HasColumnType("TEXT"); + + b.Property("Website") + .HasColumnType("TEXT"); + + b.Property("ZipCode") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsActive"); + + b.HasIndex("Name"); + + b.HasIndex("VatNumber"); + + b.ToTable("Suppliers", (string)null); + }); + modelBuilder.Entity("Apollinare.Domain.Entities.ReportFont", b => { b.Property("Id") @@ -1281,6 +1968,136 @@ namespace Apollinare.Infrastructure.Migrations b.ToTable("Risorse"); }); + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("INTEGER"); + + b.Property("ExpectedDeliveryDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OrderDate") + .HasColumnType("TEXT"); + + b.Property("OrderNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TotalGross") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalNet") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TotalTax") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("OrderDate"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("SalesOrders", (string)null); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrderLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CustomFieldsJson") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscountPercent") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("SalesOrderId") + .HasColumnType("INTEGER"); + + b.Property("ShippedQuantity") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("TaxRate") + .HasPrecision(18, 2) + .HasColumnType("TEXT"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedBy") + .HasColumnType("TEXT"); + + b.Property("WarehouseArticleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SalesOrderId"); + + b.HasIndex("WarehouseArticleId"); + + b.ToTable("SalesOrderLines", (string)null); + }); + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => { b.Property("Id") @@ -2886,6 +3703,163 @@ namespace Apollinare.Infrastructure.Migrations b.Navigation("Module"); }); + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterialsComponent", b => + { + b.HasOne("Apollinare.Domain.Entities.Production.BillOfMaterials", "BillOfMaterials") + .WithMany("Components") + .HasForeignKey("BillOfMaterialsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "ComponentArticle") + .WithMany() + .HasForeignKey("ComponentArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BillOfMaterials"); + + b.Navigation("ComponentArticle"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.MrpSuggestion", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCycle", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCyclePhase", b => + { + b.HasOne("Apollinare.Domain.Entities.Production.ProductionCycle", "ProductionCycle") + .WithMany("Phases") + .HasForeignKey("ProductionCycleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Production.WorkCenter", "WorkCenter") + .WithMany() + .HasForeignKey("WorkCenterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ProductionCycle"); + + b.Navigation("WorkCenter"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Article"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderComponent", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Production.ProductionOrder", "ProductionOrder") + .WithMany("Components") + .HasForeignKey("ProductionOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("ProductionOrder"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrderPhase", b => + { + b.HasOne("Apollinare.Domain.Entities.Production.ProductionOrder", "ProductionOrder") + .WithMany("Phases") + .HasForeignKey("ProductionOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Production.WorkCenter", "WorkCenter") + .WithMany() + .HasForeignKey("WorkCenterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ProductionOrder"); + + b.Navigation("WorkCenter"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseLocation", "DestinationWarehouse") + .WithMany() + .HasForeignKey("DestinationWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Apollinare.Domain.Entities.Purchases.Supplier", "Supplier") + .WithMany("PurchaseOrders") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DestinationWarehouse"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Purchases.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PurchaseOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("WarehouseArticle"); + }); + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => { b.HasOne("Apollinare.Domain.Entities.TipoRisorsa", "TipoRisorsa") @@ -2896,6 +3870,36 @@ namespace Apollinare.Infrastructure.Migrations b.Navigation("TipoRisorsa"); }); + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.HasOne("Apollinare.Domain.Entities.Cliente", "Customer") + .WithMany("SalesOrders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrderLine", b => + { + b.HasOne("Apollinare.Domain.Entities.Sales.SalesOrder", "SalesOrder") + .WithMany("Lines") + .HasForeignKey("SalesOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Apollinare.Domain.Entities.Warehouse.WarehouseArticle", "WarehouseArticle") + .WithMany() + .HasForeignKey("WarehouseArticleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesOrder"); + + b.Navigation("WarehouseArticle"); + }); + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => { b.HasOne("Apollinare.Domain.Entities.TipoPasto", "TipoPasto") @@ -3178,6 +4182,8 @@ namespace Apollinare.Infrastructure.Migrations modelBuilder.Entity("Apollinare.Domain.Entities.Cliente", b => { b.Navigation("Eventi"); + + b.Navigation("SalesOrders"); }); modelBuilder.Entity("Apollinare.Domain.Entities.CodiceCategoria", b => @@ -3207,11 +4213,43 @@ namespace Apollinare.Infrastructure.Migrations b.Navigation("Eventi"); }); + modelBuilder.Entity("Apollinare.Domain.Entities.Production.BillOfMaterials", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionCycle", b => + { + b.Navigation("Phases"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Production.ProductionOrder", b => + { + b.Navigation("Components"); + + b.Navigation("Phases"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("Apollinare.Domain.Entities.Purchases.Supplier", b => + { + b.Navigation("PurchaseOrders"); + }); + modelBuilder.Entity("Apollinare.Domain.Entities.Risorsa", b => { b.Navigation("DettagliRisorse"); }); + modelBuilder.Entity("Apollinare.Domain.Entities.Sales.SalesOrder", b => + { + b.Navigation("Lines"); + }); + modelBuilder.Entity("Apollinare.Domain.Entities.TipoEvento", b => { b.Navigation("Eventi");