diff --git a/CLAUDE.md b/CLAUDE.md index 123dfbc..632b181 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,12 +46,103 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve ## Quick Start - Session Recovery -**Ultima sessione:** 29 Novembre 2025 (sera) +**Ultima sessione:** 30 Novembre 2025 **Stato progetto:** Migrazione Oracle APEX → .NET + React TypeScript in corso **Lavoro completato nell'ultima sessione:** +- **NUOVA FEATURE: Sistema Codici Automatici Configurabili** - COMPLETATO + - **Obiettivo:** Sistema admin per configurare la generazione automatica di codici (articoli, magazzini, movimenti, ecc.) + - **Backend implementato:** + - `AutoCode.cs` - Entity con pattern configurabile, prefisso, sequenza, reset periodico + - `AutoCodeService.cs` - Logica business (generazione, preview, reset, validazione pattern) + - `AutoCodesController.cs` - API REST complete + - Migration EF Core `AddAutoCodeSystem` + - Seed automatico configurazioni default per tutte le entità + - **Frontend implementato:** + - `autoCode.ts` - Types TypeScript + - `autoCodeService.ts` - API calls + - `AutoCodesAdminPage.tsx` - Pagina admin con tabella configurazioni, dialog modifica, guida pattern + - **Pattern supportati:** + - `{PREFIX}` - Prefisso configurabile + - `{SEQ:n}` - Sequenza numerica con n cifre + - `{YYYY}`, `{YY}` - Anno + - `{MM}`, `{DD}` - Mese, Giorno + - **Funzionalità:** + - Configurazione per entità (warehouse_article, stock_movement, cliente, evento, ecc.) + - Reset sequenza annuale o mensile automatico + - Preview prossimo codice senza incremento + - Reset manuale sequenza + - Abilitazione/disabilitazione per entità + - Raggruppamento per modulo nell'UI + - **API Endpoints:** + - `GET /api/autocodes` - Lista configurazioni + - `GET /api/autocodes/{entityCode}` - Dettaglio + - `GET /api/autocodes/{entityCode}/preview` - Anteprima prossimo codice + - `POST /api/autocodes/{entityCode}/generate` - Genera nuovo codice + - `PUT /api/autocodes/{id}` - Aggiorna configurazione + - `POST /api/autocodes/{entityCode}/reset-sequence` - Reset sequenza + - `GET /api/autocodes/placeholders` - Lista placeholder disponibili + - **File principali:** + - `src/Apollinare.Domain/Entities/AutoCode.cs` + - `src/Apollinare.API/Services/AutoCodeService.cs` + - `src/Apollinare.API/Controllers/AutoCodesController.cs` + - `frontend/src/pages/AutoCodesAdminPage.tsx` + +**Lavoro completato nelle sessioni precedenti (29 Novembre 2025 notte):** + +- **NUOVA FEATURE: Modulo Magazzino (warehouse)** - COMPLETATO + - **Backend implementato:** + - Entities complete in `/src/Apollinare.Domain/Entities/Warehouse/`: + - `WarehouseLocation.cs` - Magazzini fisici/logici con Type enum (Physical, Virtual, Transit) + - `WarehouseArticleCategory.cs` - Categorie gerarchiche con Color, Icon, Level, FullPath + - `WarehouseArticle.cs` - Articoli con batch/serial management flags, valorizzazione + - `ArticleBatch.cs` - Tracciabilità lotti con scadenza + - `ArticleSerial.cs` - Tracciabilità numeri seriali + - `StockLevel.cs` - Giacenze per articolo/magazzino/batch + - `StockMovement.cs` - Movimenti (Inbound/Outbound/Transfer/Adjustment) + - `StockMovementLine.cs` - Righe movimento + - `MovementReason.cs` - Causali movimento + - `ArticleBarcode.cs` - Multi-barcode support + - `StockValuation.cs` + `StockValuationLayer.cs` - Valorizzazione periodo e layer FIFO/LIFO + - `InventoryCount.cs` + `InventoryCountLine.cs` - Inventari fisici + - Service completo `WarehouseService.cs` con: + - CRUD articoli, categorie, magazzini + - Gestione movimenti (carico/scarico/trasferimento/rettifica) + - Conferma movimenti con aggiornamento giacenze + - Calcolo valorizzazione (WeightedAverage, FIFO, LIFO, StandardCost) + - Gestione partite e seriali + - Controllers REST in `/src/Apollinare.API/Modules/Warehouse/Controllers/`: + - `WarehouseLocationsController.cs` + - `WarehouseArticlesController.cs` + - `WarehouseArticleCategoriesController.cs` + - `StockMovementsController.cs` + - `StockLevelsController.cs` + - Seed dati default (magazzino principale + transito, categorie base, causali) + +- **CONFIGURAZIONE: EF Core Code First Migrations** - COMPLETATO + - **Problema:** Le tabelle venivano create manualmente invece che con migrations EF Core + - **Soluzione implementata:** + - Sostituito `db.Database.EnsureCreated()` con `db.Database.MigrateAsync()` in `Program.cs` + - Creata migration `InitialCreate` con tutte le tabelle (sistema + moduli + warehouse) + - Le migrations vengono applicate **automaticamente all'avvio** dell'applicazione + - Logging delle migrations pendenti prima dell'applicazione + - **Comandi per future migrations:** + + ```bash + # Creare nuova migration + dotnet ef migrations add NomeMigration \ + --project src/Apollinare.Infrastructure \ + --startup-project src/Apollinare.API + + # L'applicazione è AUTOMATICA all'avvio - non serve "dotnet ef database update" + ``` + + - **File modificati:** `Program.cs`, `src/Apollinare.Infrastructure/Migrations/` + +**Lavoro completato nelle sessioni precedenti (29 Novembre 2025 sera):** + - **NUOVA FEATURE: Sistema Moduli Applicativi** - COMPLETATO (continuazione) - **Obiettivo:** Sistema di modularizzazione per gestire licenze, abbonamenti e funzionalità dinamiche - **Backend implementato:** @@ -318,11 +409,14 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve **MODULI BUSINESS (PRIORITÀ ALTA):** -1. [ ] **Implementare modulo Magazzino (warehouse)** - Base per tutti gli altri -2. [ ] **Implementare modulo Acquisti (purchases)** - Dipende da Magazzino -3. [ ] **Implementare modulo Vendite (sales)** - Dipende da Magazzino -4. [ ] **Implementare modulo Produzione (production)** - Dipende da Magazzino -5. [ ] **Implementare modulo Qualità (quality)** - Indipendente +1. [x] **Implementare modulo Magazzino (warehouse)** - COMPLETATO (backend) + - Backend: Entities, Service, Controllers, API completi + - Manca: Frontend (pagine React per gestione articoli, movimenti, giacenze) +2. [ ] **Frontend modulo Magazzino** - Pagine React per warehouse +3. [ ] **Implementare modulo Acquisti (purchases)** - Dipende da Magazzino +4. [ ] **Implementare modulo Vendite (sales)** - Dipende da Magazzino +5. [ ] **Implementare modulo Produzione (production)** - Dipende da Magazzino +6. [ ] **Implementare modulo Qualità (quality)** - Indipendente **Report System (completamento):** @@ -1855,3 +1949,79 @@ public interface IWarehouseService - `ModulePurchasePage.tsx`: Rimosso `moduleService` import - `ModulesAdminPage.tsx`: Rimosso `PowerIcon`, `CheckIcon`, `CancelIcon` - **File:** Vari componenti frontend + +33. **EF Core Code First vs Database First (FIX 29/11/2025):** + - **Problema:** Le tabelle venivano create manualmente con SQL invece di usare EF Core migrations + - **Causa:** `db.Database.EnsureCreated()` non supporta migrations e crea le tabelle direttamente + - **Soluzione:** + - Sostituito `EnsureCreated()` con `MigrateAsync()` in `Program.cs` + - Rimosso database e migrations esistenti + - Creata nuova migration `InitialCreate` con `dotnet ef migrations add` + - Le migrations vengono ora applicate automaticamente all'avvio + - **File:** `Program.cs`, `src/Apollinare.Infrastructure/Migrations/20251129134709_InitialCreate.cs` + +34. **Modulo Warehouse - Struttura Completa (IMPLEMENTATO 29/11/2025):** + - **Entities in `/src/Apollinare.Domain/Entities/Warehouse/`:** + - `WarehouseLocation.cs` - Magazzini (Physical, Virtual, Transit) + - `WarehouseArticle.cs` - Articoli con batch/serial flags + - `WarehouseArticleCategory.cs` - Categorie gerarchiche + - `ArticleBatch.cs` - Lotti con scadenza + - `ArticleSerial.cs` - Numeri seriali + - `StockLevel.cs` - Giacenze + - `StockMovement.cs` + `StockMovementLine.cs` - Movimenti + - `MovementReason.cs` - Causali + - `ArticleBarcode.cs` - Multi-barcode + - `StockValuation.cs` + `StockValuationLayer.cs` - Valorizzazione + - `InventoryCount.cs` + `InventoryCountLine.cs` - Inventari + - **Service:** `WarehouseService.cs` con CRUD completo, movimenti, giacenze, valorizzazione + - **Controllers:** `WarehouseLocationsController`, `WarehouseArticlesController`, `WarehouseArticleCategoriesController`, `StockMovementsController`, `StockLevelsController` + - **API Endpoints principali:** + - `GET/POST /api/warehouse/locations` - Magazzini + - `GET/POST /api/warehouse/articles` - Articoli + - `GET/POST /api/warehouse/categories` - Categorie + - `POST /api/warehouse/movements/inbound` - Carichi + - `POST /api/warehouse/movements/outbound` - Scarichi + - `POST /api/warehouse/movements/{id}/confirm` - Conferma movimento + - `GET /api/warehouse/articles/{id}/stock` - Giacenza articolo + +35. **Sistema Codici Automatici Configurabili (IMPLEMENTATO 30/11/2025):** + - **Obiettivo:** Sistema per generare automaticamente codici univoci per tutte le entità (articoli, magazzini, movimenti, clienti, eventi, ecc.) + - **Entity:** `AutoCode.cs` in `/src/Apollinare.Domain/Entities/` + - `EntityCode` - Identificativo entità (es. "warehouse_article") + - `EntityName` - Nome visualizzato + - `Prefix` - Prefisso per {PREFIX} + - `Pattern` - Pattern con placeholder (es. "{PREFIX}{YYYY}-{SEQ:5}") + - `LastSequence` - Ultimo numero usato + - `ResetSequenceYearly` / `ResetSequenceMonthly` - Reset automatico + - `IsEnabled` - Abilita generazione + - `IsReadOnly` - Codice non modificabile + - `ModuleCode` - Raggruppa per modulo + - **Service:** `AutoCodeService.cs` in `/src/Apollinare.API/Services/` + - `GenerateNextCodeAsync(entityCode)` - Genera e incrementa + - `PreviewNextCodeAsync(entityCode)` - Anteprima senza incremento + - `IsCodeUniqueAsync(entityCode, code)` - Verifica univocità + - `ResetSequenceAsync(entityCode)` - Reset manuale + - `SeedDefaultConfigurationsAsync()` - Seed configurazioni default + - **Controller:** `AutoCodesController.cs` + - **Frontend:** + - `AutoCodesAdminPage.tsx` - Pagina admin con accordions per modulo + - `autoCodeService.ts` - API calls + - `autoCode.ts` - Types + - **Pattern supportati:** + - `{PREFIX}` - Prefisso configurabile + - `{SEQ:n}` - Sequenza con n cifre (es. {SEQ:5} → 00001) + - `{YYYY}`, `{YY}` - Anno 4 o 2 cifre + - `{MM}`, `{DD}` - Mese e giorno + - Testo statico (es. "-", "/") + - **Entità preconfigurate:** + - Core: cliente, evento, articolo + - Warehouse: warehouse_location, warehouse_article, warehouse_category, stock_movement, inventory_count, article_batch + - Purchases (future): purchase_order, supplier + - Sales (future): sales_order, invoice + - **Esempio utilizzo nel codice:** + ```csharp + // Nel service che crea un articolo + var code = await _autoCodeService.GenerateNextCodeAsync("warehouse_article"); + if (code != null) + article.Code = code; + ``` diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fbe379b..bb7c2cb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -19,6 +19,7 @@ import ReportTemplatesPage from "./pages/ReportTemplatesPage"; import ReportEditorPage from "./pages/ReportEditorPage"; import ModulesAdminPage from "./pages/ModulesAdminPage"; import ModulePurchasePage from "./pages/ModulePurchasePage"; +import AutoCodesAdminPage from "./pages/AutoCodesAdminPage"; import WarehouseRoutes from "./modules/warehouse/routes"; import { ModuleGuard } from "./components/ModuleGuard"; import { useRealTimeUpdates } from "./hooks/useRealTimeUpdates"; @@ -90,12 +91,16 @@ function App() { path="report-editor/:id" element={} /> - {/* Moduli */} + {/* Admin */} } /> } /> + } + /> {/* Warehouse Module */} , path: "/report-templates" }, { text: "Moduli", icon: , path: "/modules" }, + { text: "Codici Auto", icon: , path: "/admin/auto-codes" }, ]; export default function Layout() { diff --git a/frontend/src/modules/warehouse/pages/ArticleFormPage.tsx b/frontend/src/modules/warehouse/pages/ArticleFormPage.tsx index 22aee9b..78050cd 100644 --- a/frontend/src/modules/warehouse/pages/ArticleFormPage.tsx +++ b/frontend/src/modules/warehouse/pages/ArticleFormPage.tsx @@ -86,6 +86,7 @@ export default function ArticleFormPage() { const [tabValue, setTabValue] = useState(0); const [formData, setFormData] = useState({ code: "", + alternativeCode: "", description: "", shortDescription: "", categoryId: undefined as number | undefined, @@ -138,6 +139,7 @@ export default function ArticleFormPage() { if (article) { setFormData({ code: article.code, + alternativeCode: article.alternativeCode || "", description: article.description, shortDescription: article.shortDescription || "", categoryId: article.categoryId, @@ -190,7 +192,8 @@ export default function ArticleFormPage() { const validate = (): boolean => { const newErrors: Record = {}; - if (!formData.code.trim()) { + // Il codice è generato automaticamente, non richiede validazione in creazione + if (!isNew && !formData.code.trim()) { newErrors.code = "Il codice è obbligatorio"; } if (!formData.description.trim()) { @@ -211,9 +214,10 @@ export default function ArticleFormPage() { let savedId: number; if (isNew) { const createData: CreateArticleDto = { - code: formData.code, + // code è generato automaticamente dal backend description: formData.description, shortDescription: formData.shortDescription || undefined, + alternativeCode: formData.alternativeCode || undefined, categoryId: formData.categoryId, unitOfMeasure: formData.unitOfMeasure, barcode: formData.barcode || undefined, @@ -234,9 +238,10 @@ export default function ArticleFormPage() { savedId = result.id; } else { const updateData: UpdateArticleDto = { - code: formData.code, + // code non modificabile description: formData.description, shortDescription: formData.shortDescription || undefined, + alternativeCode: formData.alternativeCode || undefined, categoryId: formData.categoryId, unitOfMeasure: formData.unitOfMeasure, barcode: formData.barcode || undefined, @@ -327,19 +332,46 @@ export default function ArticleFormPage() { Informazioni Base - + handleChange("code", e.target.value)} - error={!!errors.code} - helperText={errors.code} - required - disabled={!isNew} + value={ + isNew ? "(Generato al salvataggio)" : formData.code + } + disabled + helperText={ + isNew + ? "Verrà assegnato automaticamente" + : "Generato automaticamente" + } + InputProps={{ + readOnly: true, + }} + sx={ + isNew + ? { + "& .MuiInputBase-input.Mui-disabled": { + fontStyle: "italic", + color: "text.secondary", + }, + } + : undefined + } /> - + + + handleChange("alternativeCode", e.target.value) + } + helperText="Opzionale" + /> + + { const newErrors: Record = {}; - if (!formData.code.trim()) { - newErrors.code = "Il codice è obbligatorio"; - } + // Il codice è generato automaticamente, non richiede validazione in creazione if (!formData.name.trim()) { newErrors.name = "Il nome è obbligatorio"; } @@ -124,12 +124,16 @@ export default function WarehouseLocationsPage() { try { if (editingWarehouse) { + // In modifica non inviamo il code (non modificabile) + const { code: _code, ...updateData } = formData; await updateMutation.mutateAsync({ id: editingWarehouse.id, - data: formData, + data: updateData, }); } else { - await createMutation.mutateAsync(formData); + // In creazione non inviamo il code (generato automaticamente dal backend) + const { code: _code, ...createData } = formData; + await createMutation.mutateAsync(createData); } handleCloseDialog(); } catch (error) { @@ -364,19 +368,46 @@ export default function WarehouseLocationsPage() { - + handleChange("code", e.target.value)} - error={!!errors.code} - helperText={errors.code} - required - disabled={!!editingWarehouse} + value={ + editingWarehouse ? formData.code : "(Generato al salvataggio)" + } + disabled + helperText={ + editingWarehouse + ? "Generato automaticamente" + : "Verrà assegnato automaticamente" + } + InputProps={{ + readOnly: true, + }} + sx={ + !editingWarehouse + ? { + "& .MuiInputBase-input.Mui-disabled": { + fontStyle: "italic", + color: "text.secondary", + }, + } + : undefined + } /> - + + + handleChange("alternativeCode", e.target.value) + } + helperText="Opzionale" + /> + + >({ attivo: true }); const { data: articoli = [], isLoading } = useQuery({ - queryKey: ['articoli'], + queryKey: ["articoli"], queryFn: () => articoliService.getAll(), }); const { data: tipiMateriale = [] } = useQuery({ - queryKey: ['lookup', 'tipi-materiale'], + queryKey: ["lookup", "tipi-materiale"], queryFn: () => lookupService.getTipiMateriale(), }); const { data: categorie = [] } = useQuery({ - queryKey: ['lookup', 'categorie'], + queryKey: ["lookup", "categorie"], queryFn: () => lookupService.getCategorie(), }); const createMutation = useMutation({ mutationFn: (data: Partial) => articoliService.create(data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['articoli'] }); + queryClient.invalidateQueries({ queryKey: ["articoli"] }); handleCloseDialog(); }, }); const updateMutation = useMutation({ - mutationFn: ({ id, data }: { id: number; data: Partial }) => articoliService.update(id, data), + mutationFn: ({ id, data }: { id: number; data: Partial }) => + articoliService.update(id, data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['articoli'] }); + queryClient.invalidateQueries({ queryKey: ["articoli"] }); handleCloseDialog(); }, }); const deleteMutation = useMutation({ mutationFn: (id: number) => articoliService.delete(id), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['articoli'] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ["articoli"] }), }); const handleCloseDialog = () => { @@ -78,35 +83,45 @@ export default function ArticoliPage() { const handleSubmit = () => { if (editingId) { - updateMutation.mutate({ id: editingId, data: formData }); + // In modifica, non inviamo il codice (non modificabile) + const { codice: _codice, ...updateData } = formData; + updateMutation.mutate({ id: editingId, data: updateData }); } else { - createMutation.mutate(formData); + // In creazione, non inviamo il codice (generato automaticamente) + const { codice: _codice, ...createData } = formData; + createMutation.mutate(createData); } }; const columns: GridColDef[] = [ - { field: 'codice', headerName: 'Codice', width: 100 }, - { field: 'descrizione', headerName: 'Descrizione', flex: 1, minWidth: 200 }, + { field: "codice", headerName: "Codice", width: 100 }, + { field: "codiceAlternativo", headerName: "Cod. Alt.", width: 100 }, + { field: "descrizione", headerName: "Descrizione", flex: 1, minWidth: 200 }, { - field: 'tipoMateriale', - headerName: 'Tipo', + field: "tipoMateriale", + headerName: "Tipo", width: 130, - valueGetter: (value: any) => value?.descrizione || '', + valueGetter: (value: any) => value?.descrizione || "", }, { - field: 'categoria', - headerName: 'Categoria', + field: "categoria", + headerName: "Categoria", width: 120, - valueGetter: (value: any) => value?.descrizione || '', + valueGetter: (value: any) => value?.descrizione || "", }, - { field: 'qtaDisponibile', headerName: 'Disponibile', width: 100, type: 'number' }, - { field: 'qtaStdA', headerName: 'Qta A', width: 80, type: 'number' }, - { field: 'qtaStdB', headerName: 'Qta B', width: 80, type: 'number' }, - { field: 'qtaStdS', headerName: 'Qta S', width: 80, type: 'number' }, - { field: 'unitaMisura', headerName: 'UM', width: 60 }, { - field: 'actions', - headerName: 'Azioni', + field: "qtaDisponibile", + headerName: "Disponibile", + width: 100, + type: "number", + }, + { field: "qtaStdA", headerName: "Qta A", width: 80, type: "number" }, + { field: "qtaStdB", headerName: "Qta B", width: 80, type: "number" }, + { field: "qtaStdS", headerName: "Qta S", width: 80, type: "number" }, + { field: "unitaMisura", headerName: "UM", width: 60 }, + { + field: "actions", + headerName: "Azioni", width: 120, sortable: false, renderCell: (params) => ( @@ -118,7 +133,7 @@ export default function ArticoliPage() { size="small" color="error" onClick={() => { - if (confirm('Eliminare questo articolo?')) { + if (confirm("Eliminare questo articolo?")) { deleteMutation.mutate(params.row.id); } }} @@ -132,14 +147,25 @@ export default function ArticoliPage() { return ( - + Articoli - - + - - {editingId ? 'Modifica Articolo' : 'Nuovo Articolo'} + + + {editingId ? "Modifica Articolo" : "Nuovo Articolo"} + - + setFormData({ ...formData, codice: e.target.value })} + value={ + editingId + ? formData.codice || "" + : "(Generato al salvataggio)" + } + disabled + helperText={ + editingId + ? "Generato automaticamente" + : "Verrà assegnato automaticamente" + } + InputProps={{ + readOnly: true, + }} + sx={ + !editingId + ? { + "& .MuiInputBase-input.Mui-disabled": { + fontStyle: "italic", + color: "text.secondary", + }, + } + : undefined + } /> - + + + setFormData({ + ...formData, + codiceAlternativo: e.target.value, + }) + } + helperText="Opzionale" + /> + + setFormData({ ...formData, descrizione: e.target.value })} + value={formData.descrizione || ""} + onChange={(e) => + setFormData({ ...formData, descrizione: e.target.value }) + } /> Tipo Materiale @@ -192,12 +269,19 @@ export default function ArticoliPage() { Categoria @@ -207,16 +291,23 @@ export default function ArticoliPage() { label="Quantità Disponibile" fullWidth type="number" - value={formData.qtaDisponibile || ''} - onChange={(e) => setFormData({ ...formData, qtaDisponibile: parseFloat(e.target.value) || undefined })} + value={formData.qtaDisponibile || ""} + onChange={(e) => + setFormData({ + ...formData, + qtaDisponibile: parseFloat(e.target.value) || undefined, + }) + } /> setFormData({ ...formData, unitaMisura: e.target.value })} + value={formData.unitaMisura || ""} + onChange={(e) => + setFormData({ ...formData, unitaMisura: e.target.value }) + } /> @@ -225,8 +316,13 @@ export default function ArticoliPage() { label="Qta Std Adulti (A)" fullWidth type="number" - value={formData.qtaStdA || ''} - onChange={(e) => setFormData({ ...formData, qtaStdA: parseFloat(e.target.value) || undefined })} + value={formData.qtaStdA || ""} + onChange={(e) => + setFormData({ + ...formData, + qtaStdA: parseFloat(e.target.value) || undefined, + }) + } /> @@ -234,8 +330,13 @@ export default function ArticoliPage() { label="Qta Std Buffet (B)" fullWidth type="number" - value={formData.qtaStdB || ''} - onChange={(e) => setFormData({ ...formData, qtaStdB: parseFloat(e.target.value) || undefined })} + value={formData.qtaStdB || ""} + onChange={(e) => + setFormData({ + ...formData, + qtaStdB: parseFloat(e.target.value) || undefined, + }) + } /> @@ -243,8 +344,13 @@ export default function ArticoliPage() { label="Qta Std Seduti (S)" fullWidth type="number" - value={formData.qtaStdS || ''} - onChange={(e) => setFormData({ ...formData, qtaStdS: parseFloat(e.target.value) || undefined })} + value={formData.qtaStdS || ""} + onChange={(e) => + setFormData({ + ...formData, + qtaStdS: parseFloat(e.target.value) || undefined, + }) + } /> @@ -253,8 +359,10 @@ export default function ArticoliPage() { fullWidth multiline rows={3} - value={formData.note || ''} - onChange={(e) => setFormData({ ...formData, note: e.target.value })} + value={formData.note || ""} + onChange={(e) => + setFormData({ ...formData, note: e.target.value }) + } /> @@ -262,7 +370,7 @@ export default function ArticoliPage() { diff --git a/frontend/src/pages/AutoCodesAdminPage.tsx b/frontend/src/pages/AutoCodesAdminPage.tsx new file mode 100644 index 0000000..1a1b1d1 --- /dev/null +++ b/frontend/src/pages/AutoCodesAdminPage.tsx @@ -0,0 +1,793 @@ +import { useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + Box, + Card, + CardContent, + Typography, + Button, + Chip, + IconButton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Alert, + CircularProgress, + Tooltip, + LinearProgress, + Switch, + FormControlLabel, + TextField, + Accordion, + AccordionSummary, + AccordionDetails, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + FormControl, + InputLabel, + Select, + MenuItem, + InputAdornment, +} from "@mui/material"; +import Grid from "@mui/material/Grid"; +import { + Refresh as RefreshIcon, + Edit as EditIcon, + RestartAlt as ResetIcon, + ExpandMore as ExpandMoreIcon, + Code as CodeIcon, + Preview as PreviewIcon, + Help as HelpIcon, + ContentCopy as CopyIcon, +} from "@mui/icons-material"; +import * as Icons from "@mui/icons-material"; +import { autoCodeService } from "../services/autoCodeService"; +import type { + AutoCodeDto, + AutoCodeUpdateDto, + PlaceholderInfo, +} from "../types/autoCode"; +import { groupByModule, moduleNames, moduleIcons } from "../types/autoCode"; + +export default function AutoCodesAdminPage() { + const queryClient = useQueryClient(); + const [editingConfig, setEditingConfig] = useState(null); + const [confirmReset, setConfirmReset] = useState(null); + const [previewCode, setPreviewCode] = useState(null); + const [showHelp, setShowHelp] = useState(false); + const [expandedModule, setExpandedModule] = useState("core"); + + // Query per tutte le configurazioni + const { + data: configs = [], + isLoading, + refetch, + } = useQuery({ + queryKey: ["autocodes"], + queryFn: () => autoCodeService.getAll(), + }); + + // Query per i placeholder disponibili + const { data: placeholders = [] } = useQuery({ + queryKey: ["autocodes", "placeholders"], + queryFn: () => autoCodeService.getPlaceholders(), + }); + + // Mutation per aggiornare configurazione + const updateMutation = useMutation({ + mutationFn: ({ id, data }: { id: number; data: AutoCodeUpdateDto }) => + autoCodeService.update(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["autocodes"] }); + setEditingConfig(null); + }, + }); + + // Mutation per reset sequenza + const resetMutation = useMutation({ + mutationFn: (entityCode: string) => + autoCodeService.resetSequence(entityCode), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["autocodes"] }); + setConfirmReset(null); + }, + }); + + // Mutation per preview codice + const previewMutation = useMutation({ + mutationFn: (entityCode: string) => autoCodeService.previewCode(entityCode), + onSuccess: (data) => { + setPreviewCode(data.code); + }, + }); + + // Raggruppa configurazioni per modulo + const groupedConfigs = groupByModule(configs); + + // Helper per ottenere icona modulo + const getModuleIcon = (moduleCode: string) => { + const iconName = moduleIcons[moduleCode] || "Extension"; + const IconComponent = (Icons as Record)[ + iconName + ]; + return IconComponent ? : ; + }; + + if (isLoading) { + return ( + + + + ); + } + + return ( + + {/* Header */} + + + + Codici Automatici + + + Configura i pattern per la generazione automatica dei codici + + + + + + + + + {/* Accordion per moduli */} + {Object.entries(groupedConfigs).map(([moduleCode, moduleConfigs]) => ( + + setExpandedModule(isExpanded ? moduleCode : false) + } + sx={{ mb: 1 }} + > + }> + + {getModuleIcon(moduleCode)} + + {moduleNames[moduleCode] || moduleCode} + + + + + + + + + + Entita + Prefisso + Pattern + Esempio + Sequenza + Reset + Stato + Azioni + + + + {moduleConfigs.map((config) => ( + + + + + {config.entityName} + + + {config.entityCode} + + + + + + + + + {config.pattern} + + + + + + {config.exampleCode} + + + + previewMutation.mutate(config.entityCode) + } + disabled={!config.isEnabled} + > + + + + + + + + {config.lastSequence} + + + + {config.resetSequenceMonthly ? ( + + ) : config.resetSequenceYearly ? ( + + ) : ( + + )} + + + + + + + setEditingConfig(config)} + > + + + + + setConfirmReset(config.entityCode)} + disabled={!config.isEnabled} + > + + + + + + ))} + +
+
+
+
+ ))} + + {/* Dialog modifica configurazione */} + setEditingConfig(null)} + onSave={(data) => { + if (editingConfig) { + updateMutation.mutate({ id: editingConfig.id, data }); + } + }} + isSaving={updateMutation.isPending} + error={updateMutation.error as Error | null} + /> + + {/* Dialog conferma reset */} + setConfirmReset(null)} + maxWidth="xs" + fullWidth + > + Conferma Reset Sequenza + + + Sei sicuro di voler resettare la sequenza per{" "} + + {configs.find((c) => c.entityCode === confirmReset)?.entityName} + + ? + + + La sequenza verra riportata a 0. Il prossimo codice generato partira + da 1. + + + + + + + + + {/* Dialog anteprima codice */} + setPreviewCode(null)} + maxWidth="xs" + fullWidth + > + Anteprima Prossimo Codice + + + + + {previewCode} + + + { + navigator.clipboard.writeText(previewCode || ""); + }} + > + + + + + + Questo e il codice che verra generato alla prossima creazione. +
+ La sequenza non e stata incrementata. +
+
+ + + +
+ + {/* Dialog guida pattern */} + setShowHelp(false)} + maxWidth="md" + fullWidth + > + Guida ai Pattern + + + I pattern definiscono come vengono generati i codici automatici. + Puoi combinare testo statico e placeholder dinamici. + + + + Placeholder Disponibili + + + + + + Placeholder + Descrizione + Esempio + + + + {placeholders.map((p) => ( + + + + {p.placeholder} + + + {p.description} + + + {p.example} + + + + ))} + +
+
+ + + Esempi di Pattern + + + + + + + {"{PREFIX}-{SEQ:5}"} + + + Risultato: ART-00001, ART-00002, ... + + + + + + + + + {"{PREFIX}{YYYY}-{SEQ:5}"} + + + Risultato: EVT2025-00001 (reset annuale) + + + + + + + + + {"{PREFIX}{YY}{MM}-{SEQ:4}"} + + + Risultato: MOV2511-0001 (reset mensile) + + + + + + + + + {"FT{YYYY}/{SEQ:5}"} + + + Risultato: FT2025/00001 (formato fattura) + + + + + +
+ + + +
+
+ ); +} + +// Dialog per modifica configurazione +interface EditConfigDialogProps { + config: AutoCodeDto | null; + placeholders: PlaceholderInfo[]; + onClose: () => void; + onSave: (data: AutoCodeUpdateDto) => void; + isSaving: boolean; + error: Error | null; +} + +function EditConfigDialog({ + config, + placeholders, + onClose, + onSave, + isSaving, + error, +}: EditConfigDialogProps) { + const [formData, setFormData] = useState({}); + + // Reset form quando cambia config + const handleOpen = () => { + if (config) { + setFormData({ + prefix: config.prefix, + pattern: config.pattern, + resetSequenceYearly: config.resetSequenceYearly, + resetSequenceMonthly: config.resetSequenceMonthly, + isEnabled: config.isEnabled, + isReadOnly: config.isReadOnly, + description: config.description, + }); + } + }; + + const handleSave = () => { + onSave(formData); + }; + + // Calcola esempio in tempo reale + const getExampleFromPattern = (pattern: string, prefix: string | null) => { + const now = new Date(); + return pattern + .replace("{PREFIX}", prefix || "") + .replace("{YEAR}", now.getFullYear().toString()) + .replace("{YYYY}", now.getFullYear().toString()) + .replace("{YY}", now.getFullYear().toString().slice(-2)) + .replace("{MONTH}", (now.getMonth() + 1).toString().padStart(2, "0")) + .replace("{MM}", (now.getMonth() + 1).toString().padStart(2, "0")) + .replace("{DAY}", now.getDate().toString().padStart(2, "0")) + .replace("{DD}", now.getDate().toString().padStart(2, "0")) + .replace(/\{SEQ:(\d+)\}/g, (_, digits) => "X".repeat(parseInt(digits))); + }; + + const currentExample = getExampleFromPattern( + formData.pattern || config?.pattern || "", + formData.prefix !== undefined ? formData.prefix : config?.prefix || null, + ); + + return ( + + {config && ( + <> + + Modifica Configurazione: {config.entityName} + + + + + + setFormData({ ...formData, prefix: e.target.value || null }) + } + fullWidth + size="small" + helperText="Testo sostituito nel placeholder {PREFIX}" + /> + + + + + setFormData({ ...formData, pattern: e.target.value }) + } + fullWidth + size="small" + helperText="Pattern per generazione codice" + InputProps={{ + sx: { fontFamily: "monospace" }, + endAdornment: ( + + + {placeholders.map((p) => ( + + {p.placeholder}: {p.description} + + ))} +
+ } + > + + + + ), + }} + /> +
+ + + + + Anteprima: + + + {currentExample} + + + + + + + Reset Sequenza + + + + + + + setFormData({ + ...formData, + description: e.target.value || null, + }) + } + fullWidth + size="small" + multiline + rows={2} + /> + + + + + setFormData({ + ...formData, + isEnabled: e.target.checked, + }) + } + /> + } + label="Generazione attiva" + /> + + + + + setFormData({ + ...formData, + isReadOnly: e.target.checked, + }) + } + /> + } + label="Codice non modificabile" + /> + +
+ + {error && ( + + {error.message || "Errore durante il salvataggio"} + + )} +
+ + + + + + )} + + ); +} diff --git a/frontend/src/pages/ClientiPage.tsx b/frontend/src/pages/ClientiPage.tsx index 815ef3d..0f9e92f 100644 --- a/frontend/src/pages/ClientiPage.tsx +++ b/frontend/src/pages/ClientiPage.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useState } from "react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Box, Typography, @@ -12,11 +12,15 @@ import { DialogActions, TextField, Grid, -} from '@mui/material'; -import { DataGrid, GridColDef } from '@mui/x-data-grid'; -import { Add as AddIcon, Edit as EditIcon, Delete as DeleteIcon } from '@mui/icons-material'; -import { clientiService } from '../services/lookupService'; -import { Cliente } from '../types'; +} from "@mui/material"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import { + Add as AddIcon, + Edit as EditIcon, + Delete as DeleteIcon, +} from "@mui/icons-material"; +import { clientiService } from "../services/lookupService"; +import { Cliente } from "../types"; export default function ClientiPage() { const queryClient = useQueryClient(); @@ -25,29 +29,30 @@ export default function ClientiPage() { const [formData, setFormData] = useState>({ attivo: true }); const { data: clienti = [], isLoading } = useQuery({ - queryKey: ['clienti'], + queryKey: ["clienti"], queryFn: () => clientiService.getAll(), }); const createMutation = useMutation({ mutationFn: (data: Partial) => clientiService.create(data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['clienti'] }); + queryClient.invalidateQueries({ queryKey: ["clienti"] }); handleCloseDialog(); }, }); const updateMutation = useMutation({ - mutationFn: ({ id, data }: { id: number; data: Partial }) => clientiService.update(id, data), + mutationFn: ({ id, data }: { id: number; data: Partial }) => + clientiService.update(id, data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['clienti'] }); + queryClient.invalidateQueries({ queryKey: ["clienti"] }); handleCloseDialog(); }, }); const deleteMutation = useMutation({ mutationFn: (id: number) => clientiService.delete(id), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['clienti'] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ["clienti"] }), }); const handleCloseDialog = () => { @@ -64,22 +69,33 @@ export default function ClientiPage() { const handleSubmit = () => { if (editingId) { - updateMutation.mutate({ id: editingId, data: formData }); + // In modifica, non inviamo il codice (non modificabile) + const { codice: _codice, ...updateData } = formData; + updateMutation.mutate({ id: editingId, data: updateData }); } else { - createMutation.mutate(formData); + // In creazione, non inviamo il codice (generato automaticamente) + const { codice: _codice, ...createData } = formData; + createMutation.mutate(createData); } }; const columns: GridColDef[] = [ - { field: 'ragioneSociale', headerName: 'Ragione Sociale', flex: 1, minWidth: 200 }, - { field: 'citta', headerName: 'Città', width: 150 }, - { field: 'provincia', headerName: 'Prov.', width: 80 }, - { field: 'telefono', headerName: 'Telefono', width: 130 }, - { field: 'email', headerName: 'Email', width: 200 }, - { field: 'partitaIva', headerName: 'P.IVA', width: 130 }, + { field: "codice", headerName: "Codice", width: 100 }, + { field: "codiceAlternativo", headerName: "Cod. Alt.", width: 100 }, { - field: 'actions', - headerName: 'Azioni', + field: "ragioneSociale", + headerName: "Ragione Sociale", + flex: 1, + minWidth: 200, + }, + { field: "citta", headerName: "Città", width: 150 }, + { field: "provincia", headerName: "Prov.", width: 80 }, + { field: "telefono", headerName: "Telefono", width: 130 }, + { field: "email", headerName: "Email", width: 200 }, + { field: "partitaIva", headerName: "P.IVA", width: 130 }, + { + field: "actions", + headerName: "Azioni", width: 120, sortable: false, renderCell: (params) => ( @@ -91,7 +107,7 @@ export default function ClientiPage() { size="small" color="error" onClick={() => { - if (confirm('Eliminare questo cliente?')) { + if (confirm("Eliminare questo cliente?")) { deleteMutation.mutate(params.row.id); } }} @@ -105,14 +121,25 @@ export default function ClientiPage() { return ( - + Clienti - - + - - {editingId ? 'Modifica Cliente' : 'Nuovo Cliente'} + + + {editingId ? "Modifica Cliente" : "Nuovo Cliente"} + - + + + + + + setFormData({ + ...formData, + codiceAlternativo: e.target.value, + }) + } + helperText="Opzionale" + /> + + setFormData({ ...formData, ragioneSociale: e.target.value })} + value={formData.ragioneSociale || ""} + onChange={(e) => + setFormData({ ...formData, ragioneSociale: e.target.value }) + } /> setFormData({ ...formData, indirizzo: e.target.value })} + value={formData.indirizzo || ""} + onChange={(e) => + setFormData({ ...formData, indirizzo: e.target.value }) + } /> setFormData({ ...formData, cap: e.target.value })} + value={formData.cap || ""} + onChange={(e) => + setFormData({ ...formData, cap: e.target.value }) + } /> setFormData({ ...formData, citta: e.target.value })} + value={formData.citta || ""} + onChange={(e) => + setFormData({ ...formData, citta: e.target.value }) + } /> setFormData({ ...formData, provincia: e.target.value })} + value={formData.provincia || ""} + onChange={(e) => + setFormData({ ...formData, provincia: e.target.value }) + } /> setFormData({ ...formData, telefono: e.target.value })} + value={formData.telefono || ""} + onChange={(e) => + setFormData({ ...formData, telefono: e.target.value }) + } /> @@ -183,40 +273,53 @@ export default function ClientiPage() { label="Email" fullWidth type="email" - value={formData.email || ''} - onChange={(e) => setFormData({ ...formData, email: e.target.value })} + value={formData.email || ""} + onChange={(e) => + setFormData({ ...formData, email: e.target.value }) + } /> setFormData({ ...formData, pec: e.target.value })} + value={formData.pec || ""} + onChange={(e) => + setFormData({ ...formData, pec: e.target.value }) + } /> setFormData({ ...formData, codiceFiscale: e.target.value })} + value={formData.codiceFiscale || ""} + onChange={(e) => + setFormData({ ...formData, codiceFiscale: e.target.value }) + } /> setFormData({ ...formData, partitaIva: e.target.value })} + value={formData.partitaIva || ""} + onChange={(e) => + setFormData({ ...formData, partitaIva: e.target.value }) + } /> setFormData({ ...formData, codiceDestinatario: e.target.value })} + value={formData.codiceDestinatario || ""} + onChange={(e) => + setFormData({ + ...formData, + codiceDestinatario: e.target.value, + }) + } /> @@ -225,8 +328,10 @@ export default function ClientiPage() { fullWidth multiline rows={3} - value={formData.note || ''} - onChange={(e) => setFormData({ ...formData, note: e.target.value })} + value={formData.note || ""} + onChange={(e) => + setFormData({ ...formData, note: e.target.value }) + } /> @@ -234,7 +339,7 @@ export default function ClientiPage() { diff --git a/frontend/src/services/autoCodeService.ts b/frontend/src/services/autoCodeService.ts new file mode 100644 index 0000000..05905bd --- /dev/null +++ b/frontend/src/services/autoCodeService.ts @@ -0,0 +1,97 @@ +import api from "./api"; +import type { + AutoCodeDto, + AutoCodeUpdateDto, + GenerateCodeResponse, + ResetSequenceRequest, + CheckUniqueResponse, + PlaceholderInfo, +} from "../types/autoCode"; + +/** + * Service per la gestione dei codici automatici + */ +export const autoCodeService = { + /** + * Ottiene tutte le configurazioni AutoCode + */ + getAll: async (): Promise => { + const response = await api.get("/autocodes"); + return response.data; + }, + + /** + * Ottiene le configurazioni per un modulo specifico + */ + getByModule: async (moduleCode: string): Promise => { + const response = await api.get(`/autocodes/module/${moduleCode}`); + return response.data; + }, + + /** + * Ottiene una configurazione specifica per codice entita + */ + getByEntityCode: async (entityCode: string): Promise => { + const response = await api.get(`/autocodes/${entityCode}`); + return response.data; + }, + + /** + * Genera un nuovo codice per un'entita + */ + generateCode: async (entityCode: string): Promise => { + const response = await api.post(`/autocodes/${entityCode}/generate`); + return response.data; + }, + + /** + * Ottiene un'anteprima del prossimo codice senza incrementare la sequenza + */ + previewCode: async (entityCode: string): Promise => { + const response = await api.get(`/autocodes/${entityCode}/preview`); + return response.data; + }, + + /** + * Aggiorna una configurazione AutoCode + */ + update: async (id: number, data: AutoCodeUpdateDto): Promise => { + const response = await api.put(`/autocodes/${id}`, data); + return response.data; + }, + + /** + * Resetta la sequenza per un'entita + */ + resetSequence: async ( + entityCode: string, + request?: ResetSequenceRequest + ): Promise<{ message: string }> => { + const response = await api.post(`/autocodes/${entityCode}/reset-sequence`, request || {}); + return response.data; + }, + + /** + * Verifica se un codice e unico per un'entita + */ + checkUnique: async ( + entityCode: string, + code: string, + excludeId?: number + ): Promise => { + const response = await api.get(`/autocodes/${entityCode}/check-unique`, { + params: { code, excludeId }, + }); + return response.data; + }, + + /** + * Ottiene i placeholder disponibili per i pattern + */ + getPlaceholders: async (): Promise => { + const response = await api.get("/autocodes/placeholders"); + return response.data; + }, +}; + +export default autoCodeService; diff --git a/frontend/src/types/autoCode.ts b/frontend/src/types/autoCode.ts new file mode 100644 index 0000000..9f9a5e8 --- /dev/null +++ b/frontend/src/types/autoCode.ts @@ -0,0 +1,94 @@ +/** + * Types per il sistema di codici automatici + */ + +export interface AutoCodeDto { + id: number; + entityCode: string; + entityName: string; + prefix: string | null; + pattern: string; + lastSequence: number; + resetSequenceYearly: boolean; + resetSequenceMonthly: boolean; + lastResetYear: number | null; + lastResetMonth: number | null; + isEnabled: boolean; + isReadOnly: boolean; + moduleCode: string | null; + description: string | null; + sortOrder: number; + exampleCode: string; + createdAt: string | null; + updatedAt: string | null; +} + +export interface AutoCodeUpdateDto { + prefix?: string | null; + pattern?: string | null; + resetSequenceYearly?: boolean; + resetSequenceMonthly?: boolean; + isEnabled?: boolean; + isReadOnly?: boolean; + description?: string | null; +} + +export interface GenerateCodeResponse { + code: string; + entityCode: string; + isPreview?: boolean; +} + +export interface ResetSequenceRequest { + newValue?: number; +} + +export interface CheckUniqueResponse { + isUnique: boolean; + code: string; + entityCode: string; +} + +export interface PlaceholderInfo { + placeholder: string; + description: string; + example: string; +} + +/** + * Raggruppa le configurazioni per modulo + */ +export function groupByModule(configs: AutoCodeDto[]): Record { + return configs.reduce((acc, config) => { + const module = config.moduleCode || "core"; + if (!acc[module]) { + acc[module] = []; + } + acc[module].push(config); + return acc; + }, {} as Record); +} + +/** + * Nomi visualizzati per i moduli + */ +export const moduleNames: Record = { + core: "Sistema Base", + warehouse: "Magazzino", + purchases: "Acquisti", + sales: "Vendite", + production: "Produzione", + quality: "Qualità", +}; + +/** + * Icone per i moduli (nomi MUI icons) + */ +export const moduleIcons: Record = { + core: "Settings", + warehouse: "Warehouse", + purchases: "ShoppingCart", + sales: "PointOfSale", + production: "Factory", + quality: "VerifiedUser", +}; diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index e64789c..939c149 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -13,6 +13,8 @@ export interface BaseEntity { } export interface Cliente extends BaseEntity { + codice: string; + codiceAlternativo?: string; ragioneSociale: string; indirizzo?: string; cap?: string; @@ -89,6 +91,7 @@ export interface Risorsa extends BaseEntity { export interface Articolo extends BaseEntity { codice: string; + codiceAlternativo?: string; descrizione: string; tipoMaterialeId?: number; tipoMateriale?: TipoMateriale; diff --git a/src/Apollinare.API/Controllers/ArticoliController.cs b/src/Apollinare.API/Controllers/ArticoliController.cs index d52b56e..c1d55f1 100644 --- a/src/Apollinare.API/Controllers/ArticoliController.cs +++ b/src/Apollinare.API/Controllers/ArticoliController.cs @@ -1,3 +1,4 @@ +using Apollinare.API.Services; using Apollinare.Domain.Entities; using Apollinare.Infrastructure.Data; using Microsoft.AspNetCore.Mvc; @@ -10,10 +11,12 @@ namespace Apollinare.API.Controllers; public class ArticoliController : ControllerBase { private readonly AppollinareDbContext _context; + private readonly AutoCodeService _autoCodeService; - public ArticoliController(AppollinareDbContext context) + public ArticoliController(AppollinareDbContext context, AutoCodeService autoCodeService) { _context = context; + _autoCodeService = autoCodeService; } [HttpGet] @@ -60,6 +63,13 @@ public class ArticoliController : ControllerBase [HttpPost] public async Task> CreateArticolo(Articolo articolo) { + // Genera codice automatico + var codice = await _autoCodeService.GenerateNextCodeAsync("articolo"); + if (!string.IsNullOrEmpty(codice)) + { + articolo.Codice = codice; + } + articolo.CreatedAt = DateTime.UtcNow; _context.Articoli.Add(articolo); await _context.SaveChangesAsync(); diff --git a/src/Apollinare.API/Controllers/AutoCodesController.cs b/src/Apollinare.API/Controllers/AutoCodesController.cs new file mode 100644 index 0000000..234f5a9 --- /dev/null +++ b/src/Apollinare.API/Controllers/AutoCodesController.cs @@ -0,0 +1,240 @@ +using Apollinare.API.Services; +using Apollinare.Domain.Entities; +using Microsoft.AspNetCore.Mvc; + +namespace Apollinare.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AutoCodesController : ControllerBase +{ + private readonly AutoCodeService _autoCodeService; + private readonly ILogger _logger; + + public AutoCodesController(AutoCodeService autoCodeService, ILogger logger) + { + _autoCodeService = autoCodeService; + _logger = logger; + } + + /// + /// Ottiene tutte le configurazioni AutoCode. + /// + [HttpGet] + public async Task>> GetAll() + { + var configs = await _autoCodeService.GetAllConfigurationsAsync(); + return Ok(configs.Select(ToDto).ToList()); + } + + /// + /// Ottiene le configurazioni AutoCode per un modulo specifico. + /// + [HttpGet("module/{moduleCode}")] + public async Task>> GetByModule(string moduleCode) + { + var configs = await _autoCodeService.GetConfigurationsByModuleAsync(moduleCode); + return Ok(configs.Select(ToDto).ToList()); + } + + /// + /// Ottiene una configurazione AutoCode specifica. + /// + [HttpGet("{entityCode}")] + public async Task> Get(string entityCode) + { + var config = await _autoCodeService.GetConfigurationAsync(entityCode); + if (config == null) + return NotFound($"Configurazione per '{entityCode}' non trovata"); + + return Ok(ToDto(config)); + } + + /// + /// Genera un nuovo codice per un'entità specifica. + /// + [HttpPost("{entityCode}/generate")] + public async Task> GenerateCode(string entityCode) + { + try + { + var code = await _autoCodeService.GenerateNextCodeAsync(entityCode); + if (code == null) + { + return BadRequest(new { error = "Generazione automatica disabilitata o configurazione non trovata" }); + } + + return Ok(new GenerateCodeResponse { Code = code, EntityCode = entityCode }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore durante la generazione del codice per {EntityCode}", entityCode); + return StatusCode(500, new { error = "Errore durante la generazione del codice" }); + } + } + + /// + /// Ottiene un'anteprima del prossimo codice senza incrementare la sequenza. + /// + [HttpGet("{entityCode}/preview")] + public async Task> PreviewCode(string entityCode) + { + var code = await _autoCodeService.PreviewNextCodeAsync(entityCode); + if (code == null) + { + return BadRequest(new { error = "Generazione automatica disabilitata o configurazione non trovata" }); + } + + return Ok(new GenerateCodeResponse { Code = code, EntityCode = entityCode, IsPreview = true }); + } + + /// + /// Aggiorna una configurazione AutoCode. + /// + [HttpPut("{id:int}")] + public async Task> Update(int id, [FromBody] AutoCodeUpdateDto dto) + { + try + { + var config = await _autoCodeService.UpdateConfigurationAsync(id, dto); + return Ok(ToDto(config)); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { error = ex.Message }); + } + catch (ArgumentException ex) + { + return BadRequest(new { error = ex.Message }); + } + } + + /// + /// Resetta la sequenza per un'entità specifica. + /// + [HttpPost("{entityCode}/reset-sequence")] + public async Task ResetSequence(string entityCode, [FromBody] ResetSequenceRequest? request) + { + try + { + await _autoCodeService.ResetSequenceAsync(entityCode, request?.NewValue ?? 0); + return Ok(new { message = $"Sequenza resettata per '{entityCode}'" }); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { error = ex.Message }); + } + } + + /// + /// Verifica se un codice è unico per un'entità. + /// + [HttpGet("{entityCode}/check-unique")] + public async Task> CheckUnique( + string entityCode, + [FromQuery] string code, + [FromQuery] int? excludeId = null) + { + var isUnique = await _autoCodeService.IsCodeUniqueAsync(entityCode, code, excludeId); + return Ok(new CheckUniqueResponse { IsUnique = isUnique, Code = code, EntityCode = entityCode }); + } + + /// + /// Ottiene i placeholder disponibili per i pattern. + /// + [HttpGet("placeholders")] + public ActionResult> GetPlaceholders() + { + var placeholders = new List + { + new() { Placeholder = "{PREFIX}", Description = "Prefisso configurato", Example = "ART" }, + new() { Placeholder = "{SEQ:n}", Description = "Sequenza numerica con n cifre", Example = "{SEQ:5} → 00001" }, + new() { Placeholder = "{YYYY}", Description = "Anno a 4 cifre", Example = "2025" }, + new() { Placeholder = "{YY}", Description = "Anno a 2 cifre", Example = "25" }, + new() { Placeholder = "{MM}", Description = "Mese a 2 cifre", Example = "11" }, + new() { Placeholder = "{DD}", Description = "Giorno a 2 cifre", Example = "29" }, + new() { Placeholder = "{YEAR}", Description = "Alias per {YYYY}", Example = "2025" }, + new() { Placeholder = "{MONTH}", Description = "Alias per {MM}", Example = "11" }, + new() { Placeholder = "{DAY}", Description = "Alias per {DD}", Example = "29" }, + }; + + return Ok(placeholders); + } + + private static AutoCodeDto ToDto(AutoCode entity) + { + return new AutoCodeDto + { + Id = entity.Id, + EntityCode = entity.EntityCode, + EntityName = entity.EntityName, + Prefix = entity.Prefix, + Pattern = entity.Pattern, + LastSequence = entity.LastSequence, + ResetSequenceYearly = entity.ResetSequenceYearly, + ResetSequenceMonthly = entity.ResetSequenceMonthly, + LastResetYear = entity.LastResetYear, + LastResetMonth = entity.LastResetMonth, + IsEnabled = entity.IsEnabled, + IsReadOnly = entity.IsReadOnly, + ModuleCode = entity.ModuleCode, + Description = entity.Description, + SortOrder = entity.SortOrder, + ExampleCode = entity.GetExampleCode(), + CreatedAt = entity.CreatedAt, + UpdatedAt = entity.UpdatedAt + }; + } +} + +#region DTOs + +public class AutoCodeDto +{ + public int Id { get; set; } + public string EntityCode { get; set; } = string.Empty; + public string EntityName { get; set; } = string.Empty; + public string? Prefix { get; set; } + public string Pattern { get; set; } = string.Empty; + public long LastSequence { get; set; } + public bool ResetSequenceYearly { get; set; } + public bool ResetSequenceMonthly { get; set; } + public int? LastResetYear { get; set; } + public int? LastResetMonth { get; set; } + public bool IsEnabled { get; set; } + public bool IsReadOnly { get; set; } + public string? ModuleCode { get; set; } + public string? Description { get; set; } + public int SortOrder { get; set; } + public string ExampleCode { get; set; } = string.Empty; + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } +} + +public class GenerateCodeResponse +{ + public string Code { get; set; } = string.Empty; + public string EntityCode { get; set; } = string.Empty; + public bool IsPreview { get; set; } +} + +public class ResetSequenceRequest +{ + public long NewValue { get; set; } = 0; +} + +public class CheckUniqueResponse +{ + public bool IsUnique { get; set; } + public string Code { get; set; } = string.Empty; + public string EntityCode { get; set; } = string.Empty; +} + +public class PlaceholderInfo +{ + public string Placeholder { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string Example { get; set; } = string.Empty; +} + +#endregion diff --git a/src/Apollinare.API/Controllers/ClientiController.cs b/src/Apollinare.API/Controllers/ClientiController.cs index 5938e65..21f25f4 100644 --- a/src/Apollinare.API/Controllers/ClientiController.cs +++ b/src/Apollinare.API/Controllers/ClientiController.cs @@ -1,3 +1,4 @@ +using Apollinare.API.Services; using Apollinare.Domain.Entities; using Apollinare.Infrastructure.Data; using Microsoft.AspNetCore.Mvc; @@ -10,10 +11,12 @@ namespace Apollinare.API.Controllers; public class ClientiController : ControllerBase { private readonly AppollinareDbContext _context; + private readonly AutoCodeService _autoCodeService; - public ClientiController(AppollinareDbContext context) + public ClientiController(AppollinareDbContext context, AutoCodeService autoCodeService) { _context = context; + _autoCodeService = autoCodeService; } [HttpGet] @@ -47,6 +50,13 @@ public class ClientiController : ControllerBase [HttpPost] public async Task> CreateCliente(Cliente cliente) { + // Genera codice automatico + var codice = await _autoCodeService.GenerateNextCodeAsync("cliente"); + if (!string.IsNullOrEmpty(codice)) + { + cliente.Codice = codice; + } + cliente.CreatedAt = DateTime.UtcNow; _context.Clienti.Add(cliente); await _context.SaveChangesAsync(); diff --git a/src/Apollinare.API/Controllers/EventiController.cs b/src/Apollinare.API/Controllers/EventiController.cs index 7fcf075..0a160e7 100644 --- a/src/Apollinare.API/Controllers/EventiController.cs +++ b/src/Apollinare.API/Controllers/EventiController.cs @@ -1,4 +1,5 @@ using Apollinare.API.Hubs; +using Apollinare.API.Services; using Apollinare.Domain.Entities; using Apollinare.Domain.Enums; using Apollinare.Infrastructure.Data; @@ -13,11 +14,13 @@ public class EventiController : ControllerBase { private readonly AppollinareDbContext _context; private readonly DataNotificationService _notifier; + private readonly AutoCodeService _autoCodeService; - public EventiController(AppollinareDbContext context, DataNotificationService notifier) + public EventiController(AppollinareDbContext context, DataNotificationService notifier, AutoCodeService autoCodeService) { _context = context; _notifier = notifier; + _autoCodeService = autoCodeService; } [HttpGet] @@ -343,6 +346,12 @@ public class EventiController : ControllerBase private async Task GeneraCodiceEvento() { + // Usa AutoCodeService per generare il codice + var generatedCode = await _autoCodeService.GenerateNextCodeAsync("evento"); + if (generatedCode != null) + return generatedCode; + + // Fallback: metodo legacy var anno = DateTime.Now.Year; var ultimoEvento = await _context.Eventi .Where(e => e.Codice != null && e.Codice.StartsWith($"EV{anno}")) diff --git a/src/Apollinare.API/Modules/Warehouse/Controllers/WarehouseArticlesController.cs b/src/Apollinare.API/Modules/Warehouse/Controllers/WarehouseArticlesController.cs index 7193a08..9c5ef63 100644 --- a/src/Apollinare.API/Modules/Warehouse/Controllers/WarehouseArticlesController.cs +++ b/src/Apollinare.API/Modules/Warehouse/Controllers/WarehouseArticlesController.cs @@ -239,6 +239,7 @@ public class WarehouseArticlesController : ControllerBase public record ArticleDto( int Id, string Code, + string? AlternativeCode, string Description, string? ShortDescription, string? Barcode, @@ -273,36 +274,36 @@ public class WarehouseArticlesController : ControllerBase ); public record CreateArticleDto( - string Code, string Description, - string? ShortDescription, - string? Barcode, - string? ManufacturerCode, - int? CategoryId, string UnitOfMeasure, - string? SecondaryUnitOfMeasure, - decimal? UnitConversionFactor, - StockManagementType StockManagement, - bool IsBatchManaged, - bool IsSerialManaged, - bool HasExpiry, - int? ExpiryWarningDays, - decimal? MinimumStock, - decimal? MaximumStock, - decimal? ReorderPoint, - decimal? ReorderQuantity, - int? LeadTimeDays, - ValuationMethod? ValuationMethod, - decimal? StandardCost, - decimal? BaseSellingPrice, - decimal? Weight, - decimal? Volume, - string? Notes + string? AlternativeCode = null, + string? ShortDescription = null, + string? Barcode = null, + string? ManufacturerCode = null, + int? CategoryId = null, + string? SecondaryUnitOfMeasure = null, + decimal? UnitConversionFactor = null, + StockManagementType StockManagement = StockManagementType.Standard, + bool IsBatchManaged = false, + bool IsSerialManaged = false, + bool HasExpiry = false, + int? ExpiryWarningDays = null, + decimal? MinimumStock = null, + decimal? MaximumStock = null, + decimal? ReorderPoint = null, + decimal? ReorderQuantity = null, + int? LeadTimeDays = null, + ValuationMethod? ValuationMethod = null, + decimal? StandardCost = null, + decimal? BaseSellingPrice = null, + decimal? Weight = null, + decimal? Volume = null, + string? Notes = null ); public record UpdateArticleDto( - string Code, string Description, + string? AlternativeCode, string? ShortDescription, string? Barcode, string? ManufacturerCode, @@ -363,6 +364,7 @@ public class WarehouseArticlesController : ControllerBase private static ArticleDto MapToDto(WarehouseArticle article) => new( article.Id, article.Code, + article.AlternativeCode, article.Description, article.ShortDescription, article.Barcode, @@ -398,7 +400,8 @@ public class WarehouseArticlesController : ControllerBase private static WarehouseArticle MapFromDto(CreateArticleDto dto) => new() { - Code = dto.Code, + // Code viene generato automaticamente da WarehouseService.CreateArticleAsync + AlternativeCode = dto.AlternativeCode, Description = dto.Description, ShortDescription = dto.ShortDescription, Barcode = dto.Barcode, @@ -428,7 +431,8 @@ public class WarehouseArticlesController : ControllerBase private static void UpdateFromDto(WarehouseArticle article, UpdateArticleDto dto) { - article.Code = dto.Code; + // Code non viene aggiornato - è generato automaticamente e immutabile + article.AlternativeCode = dto.AlternativeCode; article.Description = dto.Description; article.ShortDescription = dto.ShortDescription; article.Barcode = dto.Barcode; diff --git a/src/Apollinare.API/Modules/Warehouse/Services/WarehouseService.cs b/src/Apollinare.API/Modules/Warehouse/Services/WarehouseService.cs index be59d14..c076c06 100644 --- a/src/Apollinare.API/Modules/Warehouse/Services/WarehouseService.cs +++ b/src/Apollinare.API/Modules/Warehouse/Services/WarehouseService.cs @@ -1,3 +1,4 @@ +using Apollinare.API.Services; using Apollinare.Domain.Entities.Warehouse; using Apollinare.Infrastructure.Data; using Microsoft.EntityFrameworkCore; @@ -13,6 +14,7 @@ public class WarehouseService : IWarehouseService private readonly AppollinareDbContext _context; private readonly IMemoryCache _cache; private readonly ILogger _logger; + private readonly AutoCodeService _autoCodeService; private const string WAREHOUSES_CACHE_KEY = "warehouse_locations"; private const string CATEGORIES_CACHE_KEY = "warehouse_categories"; @@ -22,11 +24,13 @@ public class WarehouseService : IWarehouseService public WarehouseService( AppollinareDbContext context, IMemoryCache cache, - ILogger logger) + ILogger logger, + AutoCodeService autoCodeService) { _context = context; _cache = cache; _logger = logger; + _autoCodeService = autoCodeService; } #region Articoli @@ -118,6 +122,16 @@ public class WarehouseService : IWarehouseService public async Task CreateArticleAsync(WarehouseArticle article) { + // Genera codice automaticamente se non specificato + if (string.IsNullOrWhiteSpace(article.Code)) + { + var generatedCode = await _autoCodeService.GenerateNextCodeAsync("warehouse_article"); + if (generatedCode != null) + article.Code = generatedCode; + else + throw new InvalidOperationException("Impossibile generare codice automatico per l'articolo"); + } + // Verifica unicità codice if (await _context.WarehouseArticles.AnyAsync(a => a.Code == article.Code)) throw new InvalidOperationException($"Esiste già un articolo con codice '{article.Code}'"); @@ -230,6 +244,16 @@ public class WarehouseService : IWarehouseService public async Task CreateCategoryAsync(WarehouseArticleCategory category) { + // Genera codice automaticamente se non specificato + if (string.IsNullOrWhiteSpace(category.Code)) + { + var generatedCode = await _autoCodeService.GenerateNextCodeAsync("warehouse_category"); + if (generatedCode != null) + category.Code = generatedCode; + else + throw new InvalidOperationException("Impossibile generare codice automatico per la categoria"); + } + if (await _context.WarehouseArticleCategories.AnyAsync(c => c.Code == category.Code)) throw new InvalidOperationException($"Esiste già una categoria con codice '{category.Code}'"); @@ -336,6 +360,16 @@ public class WarehouseService : IWarehouseService public async Task CreateWarehouseAsync(WarehouseLocation warehouse) { + // Genera codice automaticamente se non specificato + if (string.IsNullOrWhiteSpace(warehouse.Code)) + { + var generatedCode = await _autoCodeService.GenerateNextCodeAsync("warehouse_location"); + if (generatedCode != null) + warehouse.Code = generatedCode; + else + throw new InvalidOperationException("Impossibile generare codice automatico per il magazzino"); + } + if (await _context.WarehouseLocations.AnyAsync(w => w.Code == warehouse.Code)) throw new InvalidOperationException($"Esiste già un magazzino con codice '{warehouse.Code}'"); @@ -464,6 +498,16 @@ public class WarehouseService : IWarehouseService if (!article.IsBatchManaged) throw new InvalidOperationException("L'articolo non è gestito a lotti"); + // Genera numero lotto automaticamente se non specificato + if (string.IsNullOrWhiteSpace(batch.BatchNumber)) + { + var generatedCode = await _autoCodeService.GenerateNextCodeAsync("article_batch"); + if (generatedCode != null) + batch.BatchNumber = generatedCode; + else + throw new InvalidOperationException("Impossibile generare numero lotto automatico"); + } + // Verifica unicità batch number per articolo if (await _context.ArticleBatches.AnyAsync(b => b.ArticleId == batch.ArticleId && b.BatchNumber == batch.BatchNumber)) throw new InvalidOperationException($"Esiste già un lotto '{batch.BatchNumber}' per questo articolo"); @@ -809,9 +853,15 @@ public class WarehouseService : IWarehouseService public async Task CreateMovementAsync(StockMovement movement) { - // Genera numero documento se non specificato + // Genera numero documento automaticamente se non specificato if (string.IsNullOrEmpty(movement.DocumentNumber)) - movement.DocumentNumber = await GenerateDocumentNumberAsync(movement.Type); + { + var generatedCode = await _autoCodeService.GenerateNextCodeAsync("stock_movement"); + if (generatedCode != null) + movement.DocumentNumber = generatedCode; + else + movement.DocumentNumber = await GenerateDocumentNumberAsync(movement.Type); // Fallback + } // Verifica unicità documento if (await _context.StockMovements.AnyAsync(m => m.DocumentNumber == movement.DocumentNumber)) @@ -1428,8 +1478,15 @@ public class WarehouseService : IWarehouseService public async Task CreateInventoryCountAsync(InventoryCount inventory) { + // Genera codice automaticamente se non specificato if (string.IsNullOrEmpty(inventory.Code)) - inventory.Code = $"INV/{DateTime.UtcNow:yyyyMMdd}/{await GenerateInventorySequenceAsync()}"; + { + var generatedCode = await _autoCodeService.GenerateNextCodeAsync("inventory_count"); + if (generatedCode != null) + inventory.Code = generatedCode; + else + inventory.Code = $"INV/{DateTime.UtcNow:yyyyMMdd}/{await GenerateInventorySequenceAsync()}"; // Fallback + } inventory.CreatedAt = DateTime.UtcNow; _context.InventoryCounts.Add(inventory); diff --git a/src/Apollinare.API/Program.cs b/src/Apollinare.API/Program.cs index 44c699e..c6478dd 100644 --- a/src/Apollinare.API/Program.cs +++ b/src/Apollinare.API/Program.cs @@ -19,6 +19,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddSingleton(); // Warehouse Module Services @@ -100,6 +101,10 @@ using (var scope = app.Services.CreateScope()) // Seed warehouse default data var warehouseService = scope.ServiceProvider.GetRequiredService(); await warehouseService.SeedDefaultDataAsync(); + + // Seed AutoCode configurations + var autoCodeService = scope.ServiceProvider.GetRequiredService(); + await autoCodeService.SeedDefaultConfigurationsAsync(); } if (app.Environment.IsDevelopment()) diff --git a/src/Apollinare.API/Services/AutoCodeService.cs b/src/Apollinare.API/Services/AutoCodeService.cs new file mode 100644 index 0000000..b9684ac --- /dev/null +++ b/src/Apollinare.API/Services/AutoCodeService.cs @@ -0,0 +1,489 @@ +using System.Text.RegularExpressions; +using Apollinare.Domain.Entities; +using Apollinare.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Apollinare.API.Services; + +/// +/// Servizio per la generazione automatica di codici univoci. +/// Thread-safe grazie all'uso di transazioni database. +/// +public class AutoCodeService +{ + private readonly AppollinareDbContext _db; + private readonly ILogger _logger; + private static readonly Regex SequencePattern = new(@"\{SEQ:(\d+)\}", RegexOptions.Compiled); + + public AutoCodeService(AppollinareDbContext db, ILogger logger) + { + _db = db; + _logger = logger; + } + + /// + /// Genera il prossimo codice per un'entità specifica. + /// Incrementa automaticamente la sequenza e gestisce i reset periodici. + /// + /// Codice dell'entità (es. "warehouse_article") + /// Il nuovo codice generato, o null se la generazione automatica è disabilitata + public async Task GenerateNextCodeAsync(string entityCode) + { + // Usa una transazione per garantire atomicità + await using var transaction = await _db.Database.BeginTransactionAsync(); + + try + { + var config = await _db.AutoCodes + .FirstOrDefaultAsync(c => c.EntityCode == entityCode); + + if (config == null) + { + _logger.LogWarning("Configurazione AutoCode non trovata per entità: {EntityCode}", entityCode); + return null; + } + + if (!config.IsEnabled) + { + _logger.LogDebug("Generazione automatica disabilitata per entità: {EntityCode}", entityCode); + return null; + } + + var now = DateTime.Now; + + // Gestione reset sequenza + if (ShouldResetSequence(config, now)) + { + config.LastSequence = 0; + config.LastResetYear = now.Year; + config.LastResetMonth = now.Month; + _logger.LogInformation("Sequenza resettata per entità {EntityCode} (Anno: {Year}, Mese: {Month})", + entityCode, now.Year, now.Month); + } + + // Incrementa sequenza + config.LastSequence++; + config.UpdatedAt = now; + + // Genera codice dal pattern + var code = GenerateCodeFromPattern(config.Pattern, config.Prefix, config.LastSequence, now); + + await _db.SaveChangesAsync(); + await transaction.CommitAsync(); + + _logger.LogDebug("Codice generato per {EntityCode}: {Code}", entityCode, code); + return code; + } + catch (Exception ex) + { + await transaction.RollbackAsync(); + _logger.LogError(ex, "Errore durante la generazione del codice per {EntityCode}", entityCode); + throw; + } + } + + /// + /// Genera un codice di anteprima senza incrementare la sequenza. + /// Utile per mostrare all'utente cosa verrà generato. + /// + public async Task PreviewNextCodeAsync(string entityCode) + { + var config = await _db.AutoCodes + .AsNoTracking() + .FirstOrDefaultAsync(c => c.EntityCode == entityCode); + + if (config == null || !config.IsEnabled) + return null; + + var now = DateTime.Now; + var nextSequence = config.LastSequence + 1; + + // Simula reset se necessario + if (ShouldResetSequence(config, now)) + nextSequence = 1; + + return GenerateCodeFromPattern(config.Pattern, config.Prefix, nextSequence, now); + } + + /// + /// Verifica se un codice è già utilizzato per un'entità. + /// + public async Task IsCodeUniqueAsync(string entityCode, string code, int? excludeId = null) + { + return entityCode switch + { + "warehouse_article" => !await _db.WarehouseArticles + .AnyAsync(a => a.Code == code && (excludeId == null || a.Id != excludeId)), + + "warehouse_location" => !await _db.WarehouseLocations + .AnyAsync(w => w.Code == code && (excludeId == null || w.Id != excludeId)), + + "warehouse_category" => !await _db.WarehouseArticleCategories + .AnyAsync(c => c.Code == code && (excludeId == null || c.Id != excludeId)), + + "stock_movement" => !await _db.StockMovements + .AnyAsync(m => m.DocumentNumber == code && (excludeId == null || m.Id != excludeId)), + + "inventory_count" => !await _db.InventoryCounts + .AnyAsync(i => i.Code == code && (excludeId == null || i.Id != excludeId)), + + "cliente" => !await _db.Clienti + .AnyAsync(c => c.Codice == code && (excludeId == null || c.Id != excludeId)), + + "evento" => !await _db.Eventi + .AnyAsync(e => e.Codice == code && (excludeId == null || e.Id != excludeId)), + + "articolo" => !await _db.Articoli + .AnyAsync(a => a.Codice == code && (excludeId == null || a.Id != excludeId)), + + _ => true // Entità non gestita, assume codice unico + }; + } + + /// + /// Ottiene tutte le configurazioni AutoCode. + /// + public async Task> GetAllConfigurationsAsync() + { + return await _db.AutoCodes + .OrderBy(c => c.ModuleCode) + .ThenBy(c => c.SortOrder) + .ThenBy(c => c.EntityName) + .ToListAsync(); + } + + /// + /// Ottiene le configurazioni AutoCode per un modulo specifico. + /// + public async Task> GetConfigurationsByModuleAsync(string moduleCode) + { + return await _db.AutoCodes + .Where(c => c.ModuleCode == moduleCode) + .OrderBy(c => c.SortOrder) + .ThenBy(c => c.EntityName) + .ToListAsync(); + } + + /// + /// Ottiene una configurazione AutoCode per codice entità. + /// + public async Task GetConfigurationAsync(string entityCode) + { + return await _db.AutoCodes + .FirstOrDefaultAsync(c => c.EntityCode == entityCode); + } + + /// + /// Aggiorna una configurazione AutoCode. + /// + public async Task UpdateConfigurationAsync(int id, AutoCodeUpdateDto dto) + { + var config = await _db.AutoCodes.FindAsync(id) + ?? throw new KeyNotFoundException($"Configurazione AutoCode con ID {id} non trovata"); + + if (dto.Prefix != null) + config.Prefix = dto.Prefix; + + if (dto.Pattern != null) + { + // Valida il pattern + ValidatePattern(dto.Pattern); + config.Pattern = dto.Pattern; + } + + if (dto.ResetSequenceYearly.HasValue) + config.ResetSequenceYearly = dto.ResetSequenceYearly.Value; + + if (dto.ResetSequenceMonthly.HasValue) + config.ResetSequenceMonthly = dto.ResetSequenceMonthly.Value; + + if (dto.IsEnabled.HasValue) + config.IsEnabled = dto.IsEnabled.Value; + + if (dto.IsReadOnly.HasValue) + config.IsReadOnly = dto.IsReadOnly.Value; + + if (dto.Description != null) + config.Description = dto.Description; + + config.UpdatedAt = DateTime.Now; + + await _db.SaveChangesAsync(); + return config; + } + + /// + /// Resetta manualmente la sequenza per un'entità. + /// + public async Task ResetSequenceAsync(string entityCode, long newValue = 0) + { + var config = await _db.AutoCodes + .FirstOrDefaultAsync(c => c.EntityCode == entityCode) + ?? throw new KeyNotFoundException($"Configurazione AutoCode per '{entityCode}' non trovata"); + + config.LastSequence = newValue; + config.LastResetYear = DateTime.Now.Year; + config.LastResetMonth = DateTime.Now.Month; + config.UpdatedAt = DateTime.Now; + + await _db.SaveChangesAsync(); + + _logger.LogInformation("Sequenza resettata manualmente per {EntityCode} a {Value}", entityCode, newValue); + } + + /// + /// Inizializza le configurazioni di default per tutte le entità. + /// Chiamato al seed dell'applicazione. + /// + public async Task SeedDefaultConfigurationsAsync() + { + var defaults = new List + { + // Core + new() + { + EntityCode = "cliente", + EntityName = "Cliente", + Prefix = "CLI", + Pattern = "{PREFIX}-{SEQ:5}", + ModuleCode = "core", + Description = "Codice cliente (es. CLI-00001)", + SortOrder = 10 + }, + new() + { + EntityCode = "evento", + EntityName = "Evento", + Prefix = "EVT", + Pattern = "{PREFIX}{YYYY}-{SEQ:5}", + ResetSequenceYearly = true, + ModuleCode = "core", + Description = "Codice evento con anno (es. EVT2025-00001)", + SortOrder = 20 + }, + new() + { + EntityCode = "articolo", + EntityName = "Articolo (Legacy)", + Prefix = "ART", + Pattern = "{PREFIX}-{SEQ:5}", + ModuleCode = "core", + Description = "Codice articolo legacy (es. ART-00001)", + SortOrder = 30 + }, + + // Warehouse module + new() + { + EntityCode = "warehouse_location", + EntityName = "Magazzino", + Prefix = "MAG", + Pattern = "{PREFIX}-{SEQ:3}", + ModuleCode = "warehouse", + Description = "Codice magazzino (es. MAG-001)", + SortOrder = 10 + }, + new() + { + EntityCode = "warehouse_article", + EntityName = "Articolo Magazzino", + Prefix = "WA", + Pattern = "{PREFIX}{SEQ:6}", + ModuleCode = "warehouse", + Description = "Codice articolo magazzino (es. WA000001)", + SortOrder = 20 + }, + new() + { + EntityCode = "warehouse_category", + EntityName = "Categoria Articolo", + Prefix = "CAT", + Pattern = "{PREFIX}-{SEQ:4}", + ModuleCode = "warehouse", + Description = "Codice categoria (es. CAT-0001)", + SortOrder = 30 + }, + new() + { + EntityCode = "stock_movement", + EntityName = "Movimento Magazzino", + Prefix = "MOV", + Pattern = "{PREFIX}{YYYY}{MM}-{SEQ:5}", + ResetSequenceMonthly = true, + ModuleCode = "warehouse", + Description = "Numero documento movimento (es. MOV202511-00001)", + SortOrder = 40 + }, + new() + { + EntityCode = "inventory_count", + EntityName = "Inventario", + Prefix = "INV", + Pattern = "{PREFIX}{YYYY}-{SEQ:4}", + ResetSequenceYearly = true, + ModuleCode = "warehouse", + Description = "Codice inventario (es. INV2025-0001)", + SortOrder = 50 + }, + new() + { + EntityCode = "article_batch", + EntityName = "Lotto Articolo", + Prefix = "LOT", + Pattern = "{PREFIX}{YYYY}{MM}{DD}-{SEQ:4}", + ResetSequenceMonthly = true, + ModuleCode = "warehouse", + Description = "Numero lotto (es. LOT20251129-0001)", + SortOrder = 60 + }, + + // Future: Purchases module + new() + { + EntityCode = "purchase_order", + EntityName = "Ordine Acquisto", + Prefix = "ODA", + Pattern = "{PREFIX}{YYYY}-{SEQ:5}", + ResetSequenceYearly = true, + ModuleCode = "purchases", + Description = "Numero ordine acquisto (es. ODA2025-00001)", + SortOrder = 10, + IsEnabled = false // Disabilitato finché il modulo non è implementato + }, + new() + { + EntityCode = "supplier", + EntityName = "Fornitore", + Prefix = "FOR", + Pattern = "{PREFIX}-{SEQ:5}", + ModuleCode = "purchases", + Description = "Codice fornitore (es. FOR-00001)", + SortOrder = 20, + IsEnabled = false + }, + + // Future: Sales module + new() + { + EntityCode = "sales_order", + EntityName = "Ordine Vendita", + Prefix = "ODV", + Pattern = "{PREFIX}{YYYY}-{SEQ:5}", + ResetSequenceYearly = true, + ModuleCode = "sales", + Description = "Numero ordine vendita (es. ODV2025-00001)", + SortOrder = 10, + IsEnabled = false + }, + new() + { + EntityCode = "invoice", + EntityName = "Fattura", + Prefix = "FT", + Pattern = "{PREFIX}{YYYY}/{SEQ:5}", + ResetSequenceYearly = true, + ModuleCode = "sales", + Description = "Numero fattura (es. FT2025/00001)", + SortOrder = 20, + IsEnabled = false + }, + }; + + foreach (var config in defaults) + { + var exists = await _db.AutoCodes.AnyAsync(c => c.EntityCode == config.EntityCode); + if (!exists) + { + config.CreatedAt = DateTime.Now; + _db.AutoCodes.Add(config); + _logger.LogInformation("Configurazione AutoCode creata per {EntityCode}", config.EntityCode); + } + } + + await _db.SaveChangesAsync(); + } + + #region Private Methods + + private bool ShouldResetSequence(AutoCode config, DateTime now) + { + if (config.ResetSequenceMonthly) + { + return config.LastResetYear != now.Year || config.LastResetMonth != now.Month; + } + + if (config.ResetSequenceYearly) + { + return config.LastResetYear != now.Year; + } + + return false; + } + + private static string GenerateCodeFromPattern(string pattern, string? prefix, long sequence, DateTime date) + { + var code = pattern + .Replace("{PREFIX}", prefix ?? "") + .Replace("{YEAR}", date.Year.ToString()) + .Replace("{YYYY}", date.Year.ToString()) + .Replace("{YY}", date.Year.ToString().Substring(2)) + .Replace("{MONTH}", date.Month.ToString("D2")) + .Replace("{MM}", date.Month.ToString("D2")) + .Replace("{DAY}", date.Day.ToString("D2")) + .Replace("{DD}", date.Day.ToString("D2")); + + // Gestisci {SEQ:n} + code = SequencePattern.Replace(code, match => + { + var digits = int.Parse(match.Groups[1].Value); + return sequence.ToString($"D{digits}"); + }); + + return code; + } + + private static void ValidatePattern(string pattern) + { + // Verifica che ci sia almeno un placeholder sequenza + if (!SequencePattern.IsMatch(pattern)) + { + throw new ArgumentException( + "Il pattern deve contenere almeno un placeholder {SEQ:n} per la sequenza numerica"); + } + + // Verifica che i placeholder siano validi + var validPlaceholders = new[] + { + "{PREFIX}", "{YEAR}", "{YYYY}", "{YY}", + "{MONTH}", "{MM}", "{DAY}", "{DD}" + }; + + var placeholderRegex = new Regex(@"\{[^}]+\}"); + var matches = placeholderRegex.Matches(pattern); + + foreach (Match match in matches) + { + var placeholder = match.Value; + if (!SequencePattern.IsMatch(placeholder) && !validPlaceholders.Contains(placeholder)) + { + throw new ArgumentException($"Placeholder non valido: {placeholder}"); + } + } + } + + #endregion +} + +/// +/// DTO per l'aggiornamento di una configurazione AutoCode. +/// +public class AutoCodeUpdateDto +{ + public string? Prefix { get; set; } + public string? Pattern { get; set; } + public bool? ResetSequenceYearly { get; set; } + public bool? ResetSequenceMonthly { get; set; } + public bool? IsEnabled { get; set; } + public bool? IsReadOnly { get; set; } + public string? Description { get; set; } +} diff --git a/src/Apollinare.API/apollinare.db-shm b/src/Apollinare.API/apollinare.db-shm index 05ea21c..4b9e522 100644 Binary files a/src/Apollinare.API/apollinare.db-shm and b/src/Apollinare.API/apollinare.db-shm differ diff --git a/src/Apollinare.API/apollinare.db-wal b/src/Apollinare.API/apollinare.db-wal index 42a4026..114b37d 100644 Binary files a/src/Apollinare.API/apollinare.db-wal and b/src/Apollinare.API/apollinare.db-wal differ diff --git a/src/Apollinare.Domain/Entities/Articolo.cs b/src/Apollinare.Domain/Entities/Articolo.cs index 37e3a37..c85cc96 100644 --- a/src/Apollinare.Domain/Entities/Articolo.cs +++ b/src/Apollinare.Domain/Entities/Articolo.cs @@ -2,7 +2,16 @@ namespace Apollinare.Domain.Entities; public class Articolo : BaseEntity { + /// + /// Codice articolo - generato automaticamente + /// public string Codice { get; set; } = string.Empty; + + /// + /// Codice alternativo (opzionale, inserito dall'utente) + /// + public string? CodiceAlternativo { get; set; } + public string Descrizione { get; set; } = string.Empty; public int? TipoMaterialeId { get; set; } public int? CategoriaId { get; set; } diff --git a/src/Apollinare.Domain/Entities/AutoCode.cs b/src/Apollinare.Domain/Entities/AutoCode.cs new file mode 100644 index 0000000..2659359 --- /dev/null +++ b/src/Apollinare.Domain/Entities/AutoCode.cs @@ -0,0 +1,117 @@ +namespace Apollinare.Domain.Entities; + +/// +/// Configurazione per la generazione automatica di codici. +/// Ogni entità può avere la propria configurazione con pattern personalizzabile. +/// +/// Pattern supportati: +/// - {SEQ:n} - Sequenza numerica con n cifre (es. {SEQ:5} → 00001) +/// - {YEAR} o {YYYY} - Anno corrente a 4 cifre +/// - {YY} - Anno corrente a 2 cifre +/// - {MONTH} o {MM} - Mese corrente a 2 cifre +/// - {DAY} o {DD} - Giorno corrente a 2 cifre +/// - {PREFIX} - Usa il prefisso definito +/// - Testo statico (es. "ART-", "-", "/") +/// +/// Esempi di pattern: +/// - "ART-{YYYY}-{SEQ:5}" → ART-2025-00001 +/// - "{PREFIX}{YY}{MM}{SEQ:4}" → MAG2511-0001 +/// - "CLI/{YYYY}/{SEQ:6}" → CLI/2025/000001 +/// +public class AutoCode : BaseEntity +{ + /// + /// Codice univoco dell'entità (es. "warehouse_article", "warehouse_location", "cliente") + /// + public string EntityCode { get; set; } = string.Empty; + + /// + /// Nome visualizzato dell'entità (es. "Articolo Magazzino", "Magazzino", "Cliente") + /// + public string EntityName { get; set; } = string.Empty; + + /// + /// Prefisso opzionale da usare nel pattern con {PREFIX} + /// + public string? Prefix { get; set; } + + /// + /// Pattern per la generazione del codice + /// + public string Pattern { get; set; } = "{PREFIX}{SEQ:5}"; + + /// + /// Ultimo numero di sequenza utilizzato (per {SEQ:n}) + /// + public long LastSequence { get; set; } = 0; + + /// + /// Se true, la sequenza viene resettata ogni anno + /// + public bool ResetSequenceYearly { get; set; } = false; + + /// + /// Se true, la sequenza viene resettata ogni mese + /// + public bool ResetSequenceMonthly { get; set; } = false; + + /// + /// Anno dell'ultimo reset della sequenza + /// + public int? LastResetYear { get; set; } + + /// + /// Mese dell'ultimo reset della sequenza (se ResetSequenceMonthly) + /// + public int? LastResetMonth { get; set; } + + /// + /// Se true, la generazione automatica è abilitata + /// + public bool IsEnabled { get; set; } = true; + + /// + /// Se true, il codice non può essere modificato manualmente + /// + public bool IsReadOnly { get; set; } = false; + + /// + /// Modulo di appartenenza (es. "core", "warehouse", "purchases") + /// + public string? ModuleCode { get; set; } + + /// + /// Descrizione della configurazione + /// + public string? Description { get; set; } + + /// + /// Ordine di visualizzazione nel pannello admin + /// + public int SortOrder { get; set; } = 0; + + /// + /// Esempio di codice generato (calcolato, non persistito) + /// + public string GetExampleCode() + { + var now = DateTime.Now; + return Pattern + .Replace("{PREFIX}", Prefix ?? "") + .Replace("{YEAR}", now.Year.ToString()) + .Replace("{YYYY}", now.Year.ToString()) + .Replace("{YY}", now.Year.ToString().Substring(2)) + .Replace("{MONTH}", now.Month.ToString("D2")) + .Replace("{MM}", now.Month.ToString("D2")) + .Replace("{DAY}", now.Day.ToString("D2")) + .Replace("{DD}", now.Day.ToString("D2")) + .Replace("{SEQ:1}", "X") + .Replace("{SEQ:2}", "XX") + .Replace("{SEQ:3}", "XXX") + .Replace("{SEQ:4}", "XXXX") + .Replace("{SEQ:5}", "XXXXX") + .Replace("{SEQ:6}", "XXXXXX") + .Replace("{SEQ:7}", "XXXXXXX") + .Replace("{SEQ:8}", "XXXXXXXX"); + } +} diff --git a/src/Apollinare.Domain/Entities/Cliente.cs b/src/Apollinare.Domain/Entities/Cliente.cs index 404cc04..c17d301 100644 --- a/src/Apollinare.Domain/Entities/Cliente.cs +++ b/src/Apollinare.Domain/Entities/Cliente.cs @@ -2,6 +2,16 @@ namespace Apollinare.Domain.Entities; public class Cliente : BaseEntity { + /// + /// Codice cliente - generato automaticamente + /// + public string Codice { get; set; } = string.Empty; + + /// + /// Codice alternativo (opzionale, inserito dall'utente) + /// + public string? CodiceAlternativo { get; set; } + public string RagioneSociale { get; set; } = string.Empty; public string? Indirizzo { get; set; } public string? Cap { get; set; } diff --git a/src/Apollinare.Domain/Entities/Warehouse/WarehouseArticle.cs b/src/Apollinare.Domain/Entities/Warehouse/WarehouseArticle.cs index 6244e06..3ef36f1 100644 --- a/src/Apollinare.Domain/Entities/Warehouse/WarehouseArticle.cs +++ b/src/Apollinare.Domain/Entities/Warehouse/WarehouseArticle.cs @@ -6,10 +6,15 @@ namespace Apollinare.Domain.Entities.Warehouse; public class WarehouseArticle : BaseEntity { /// - /// Codice univoco articolo (SKU) + /// Codice univoco articolo (SKU) - generato automaticamente /// public string Code { get; set; } = string.Empty; + /// + /// Codice alternativo (opzionale, inserito dall'utente) + /// + public string? AlternativeCode { get; set; } + /// /// Descrizione articolo /// diff --git a/src/Apollinare.Domain/Entities/Warehouse/WarehouseArticleCategory.cs b/src/Apollinare.Domain/Entities/Warehouse/WarehouseArticleCategory.cs index 39d83a1..a730534 100644 --- a/src/Apollinare.Domain/Entities/Warehouse/WarehouseArticleCategory.cs +++ b/src/Apollinare.Domain/Entities/Warehouse/WarehouseArticleCategory.cs @@ -6,10 +6,15 @@ namespace Apollinare.Domain.Entities.Warehouse; public class WarehouseArticleCategory : BaseEntity { /// - /// Codice categoria + /// Codice categoria - generato automaticamente /// public string Code { get; set; } = string.Empty; + /// + /// Codice alternativo (opzionale, inserito dall'utente) + /// + public string? AlternativeCode { get; set; } + /// /// Nome categoria /// diff --git a/src/Apollinare.Domain/Entities/Warehouse/WarehouseLocation.cs b/src/Apollinare.Domain/Entities/Warehouse/WarehouseLocation.cs index 093a56a..94f9048 100644 --- a/src/Apollinare.Domain/Entities/Warehouse/WarehouseLocation.cs +++ b/src/Apollinare.Domain/Entities/Warehouse/WarehouseLocation.cs @@ -6,10 +6,15 @@ namespace Apollinare.Domain.Entities.Warehouse; public class WarehouseLocation : BaseEntity { /// - /// Codice univoco del magazzino (es. "MAG01", "CENTRALE") + /// Codice univoco del magazzino - generato automaticamente /// public string Code { get; set; } = string.Empty; + /// + /// Codice alternativo (opzionale, inserito dall'utente) + /// + public string? AlternativeCode { get; set; } + /// /// Nome descrittivo del magazzino /// diff --git a/src/Apollinare.Infrastructure/Data/AppollinareDbContext.cs b/src/Apollinare.Infrastructure/Data/AppollinareDbContext.cs index dcfd3a4..0c49e4c 100644 --- a/src/Apollinare.Infrastructure/Data/AppollinareDbContext.cs +++ b/src/Apollinare.Infrastructure/Data/AppollinareDbContext.cs @@ -41,6 +41,9 @@ public class AppollinareDbContext : DbContext public DbSet AppModules => Set(); public DbSet ModuleSubscriptions => Set(); + // Auto Code system + public DbSet AutoCodes => Set(); + // Warehouse module entities public DbSet WarehouseLocations => Set(); public DbSet WarehouseArticles => Set(); @@ -274,6 +277,13 @@ public class AppollinareDbContext : DbContext .OnDelete(DeleteBehavior.Cascade); }); + // AutoCode + modelBuilder.Entity(entity => + { + entity.HasIndex(e => e.EntityCode).IsUnique(); + entity.HasIndex(e => e.ModuleCode); + }); + // =============================================== // WAREHOUSE MODULE ENTITIES // =============================================== diff --git a/src/Apollinare.Infrastructure/Migrations/20251129135918_AddAutoCodeSystem.Designer.cs b/src/Apollinare.Infrastructure/Migrations/20251129135918_AddAutoCodeSystem.Designer.cs new file mode 100644 index 0000000..ede0bed --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251129135918_AddAutoCodeSystem.Designer.cs @@ -0,0 +1,3091 @@ +// +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("20251129135918_AddAutoCodeSystem")] + partial class AddAutoCodeSystem + { + /// + 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("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("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .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("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("CodiceDestinatario") + .HasColumnType("TEXT"); + + b.Property("CodiceFiscale") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .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("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("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.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("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("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("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("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("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("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("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("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("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("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.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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Color") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .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("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("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.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.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/20251129135918_AddAutoCodeSystem.cs b/src/Apollinare.Infrastructure/Migrations/20251129135918_AddAutoCodeSystem.cs new file mode 100644 index 0000000..70c822a --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251129135918_AddAutoCodeSystem.cs @@ -0,0 +1,63 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + /// + public partial class AddAutoCodeSystem : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AutoCodes", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + EntityCode = table.Column(type: "TEXT", nullable: false), + EntityName = table.Column(type: "TEXT", nullable: false), + Prefix = table.Column(type: "TEXT", nullable: true), + Pattern = table.Column(type: "TEXT", nullable: false), + LastSequence = table.Column(type: "INTEGER", nullable: false), + ResetSequenceYearly = table.Column(type: "INTEGER", nullable: false), + ResetSequenceMonthly = table.Column(type: "INTEGER", nullable: false), + LastResetYear = table.Column(type: "INTEGER", nullable: true), + LastResetMonth = table.Column(type: "INTEGER", nullable: true), + IsEnabled = table.Column(type: "INTEGER", nullable: false), + IsReadOnly = table.Column(type: "INTEGER", nullable: false), + ModuleCode = table.Column(type: "TEXT", nullable: true), + Description = table.Column(type: "TEXT", nullable: true), + SortOrder = 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) + }, + constraints: table => + { + table.PrimaryKey("PK_AutoCodes", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_AutoCodes_EntityCode", + table: "AutoCodes", + column: "EntityCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AutoCodes_ModuleCode", + table: "AutoCodes", + column: "ModuleCode"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AutoCodes"); + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/20251129144249_AddAlternativeCodeFields.Designer.cs b/src/Apollinare.Infrastructure/Migrations/20251129144249_AddAlternativeCodeFields.Designer.cs new file mode 100644 index 0000000..6d22403 --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251129144249_AddAlternativeCodeFields.Designer.cs @@ -0,0 +1,3110 @@ +// +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("20251129144249_AddAlternativeCodeFields")] + partial class AddAlternativeCodeFields + { + /// + 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("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("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("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("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("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("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.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("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("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("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("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("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("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("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("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("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("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.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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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.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.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/20251129144249_AddAlternativeCodeFields.cs b/src/Apollinare.Infrastructure/Migrations/20251129144249_AddAlternativeCodeFields.cs new file mode 100644 index 0000000..8730d47 --- /dev/null +++ b/src/Apollinare.Infrastructure/Migrations/20251129144249_AddAlternativeCodeFields.cs @@ -0,0 +1,79 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Apollinare.Infrastructure.Migrations +{ + /// + public partial class AddAlternativeCodeFields : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AlternativeCode", + table: "WarehouseLocations", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "AlternativeCode", + table: "WarehouseArticles", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "AlternativeCode", + table: "WarehouseArticleCategories", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "Codice", + table: "Clienti", + type: "TEXT", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "CodiceAlternativo", + table: "Clienti", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "CodiceAlternativo", + table: "Articoli", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AlternativeCode", + table: "WarehouseLocations"); + + migrationBuilder.DropColumn( + name: "AlternativeCode", + table: "WarehouseArticles"); + + migrationBuilder.DropColumn( + name: "AlternativeCode", + table: "WarehouseArticleCategories"); + + migrationBuilder.DropColumn( + name: "Codice", + table: "Clienti"); + + migrationBuilder.DropColumn( + name: "CodiceAlternativo", + table: "Clienti"); + + migrationBuilder.DropColumn( + name: "CodiceAlternativo", + table: "Articoli"); + } + } +} diff --git a/src/Apollinare.Infrastructure/Migrations/AppollinareDbContextModelSnapshot.cs b/src/Apollinare.Infrastructure/Migrations/AppollinareDbContextModelSnapshot.cs index 3a8eddd..82e8df1 100644 --- a/src/Apollinare.Infrastructure/Migrations/AppollinareDbContextModelSnapshot.cs +++ b/src/Apollinare.Infrastructure/Migrations/AppollinareDbContextModelSnapshot.cs @@ -98,6 +98,9 @@ namespace Apollinare.Infrastructure.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + b.Property("CreatedAt") .HasColumnType("TEXT"); @@ -153,6 +156,79 @@ namespace Apollinare.Infrastructure.Migrations 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("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") @@ -168,6 +244,13 @@ namespace Apollinare.Infrastructure.Migrations b.Property("Citta") .HasColumnType("TEXT"); + b.Property("Codice") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodiceAlternativo") + .HasColumnType("TEXT"); + b.Property("CodiceDestinatario") .HasColumnType("TEXT"); @@ -2170,6 +2253,9 @@ namespace Apollinare.Infrastructure.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + b.Property("Barcode") .HasColumnType("TEXT"); @@ -2315,6 +2401,9 @@ namespace Apollinare.Infrastructure.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + b.Property("Code") .IsRequired() .HasColumnType("TEXT"); @@ -2386,6 +2475,9 @@ namespace Apollinare.Infrastructure.Migrations b.Property("Address") .HasColumnType("TEXT"); + b.Property("AlternativeCode") + .HasColumnType("TEXT"); + b.Property("City") .HasColumnType("TEXT");