refactor: reorganize autocodes into modules with updated UI, new translations, and backend migrations.

This commit is contained in:
2025-12-06 02:16:16 +01:00
parent 623f7b3b56
commit 6d1aef3a42
8 changed files with 4948 additions and 161 deletions

View File

@@ -47,3 +47,5 @@ File riassuntivo dello stato di sviluppo di Zentral.
- [2025-12-06 01:48:00 - Traduzione Modulo Acquisti](./devlog/2025-12-06-014800_translate_purchases.md) - **Completato** - [2025-12-06 01:48:00 - Traduzione Modulo Acquisti](./devlog/2025-12-06-014800_translate_purchases.md) - **Completato**
- [2025-12-06 01:35:00 - Fix Traduzione Tab Applicazioni](./devlog/2025-12-06-013500_fix_apps_tab_translation.md) - **Completato** - [2025-12-06 01:35:00 - Fix Traduzione Tab Applicazioni](./devlog/2025-12-06-013500_fix_apps_tab_translation.md) - **Completato**
- Corretta chiave di traduzione errata per la tab "Gestione Applicazioni" e migliorata la gestione dell'aggiornamento etichette tab. - Corretta chiave di traduzione errata per la tab "Gestione Applicazioni" e migliorata la gestione dell'aggiornamento etichette tab.
- [2025-12-06 Auto Codes Reorganization](./devlog/2025-12-06-021000_autocodes_reorg.md) - **Completato**
- Riorganizzazione UI Auto Codes, allineamento stile a Custom Fields, miglioramento traduzioni e categorizzazione.

View File

@@ -0,0 +1,30 @@
# Riorganizzazione Auto Codes
## Obiettivo
Riorganizzare la sezione "Auto Codes" per allinearla graficamente e strutturalmente alla sezione "Custom Fields", migliorando le traduzioni e la categorizzazione.
## Stato Attuale
- La pagina `AutoCodesAdminPage.tsx` funziona ma ha nomi di moduli hardcoded in `types/autoCode.ts`.
- La struttura grafica è simile ma può essere migliorata per essere identica a `CustomFieldsAdminPage`.
- Mancano alcune traduzioni e la categorizzazione potrebbe non essere aggiornata con gli ultimi moduli.
## Piano di Lavoro
1. **Analisi e Preparazione**
- [x] Identificare le differenze stilistiche tra `AutoCodesAdminPage` e `CustomFieldsAdminPage`.
- [x] Identificare le stringhe non tradotte (es. nomi moduli).
2. **Refactoring Frontend**
- [x] Aggiornare `AutoCodesAdminPage.tsx` per usare lo stesso layout di `CustomFieldsAdminPage`.
- [x] Sostituire i nomi hardcoded dei moduli con chiavi di traduzione.
- [x] Aggiornare `types/autoCode.ts` per rimuovere `appNames` hardcoded o mapparlo su chiavi i18n.
3. **Aggiornamento Traduzioni**
- [x] Aggiungere le chiavi mancanti in `public/locales/it/translation.json`.
- [x] Aggiungere le chiavi mancanti in `public/locales/en/translation.json`.
4. **Verifica**
- [x] Verificare che la pagina si carichi correttamente.
- [x] Verificare che le traduzioni funzionino.
- [x] Verificare che la categorizzazione sia corretta.
- [x] Aggiornare `AutoCodeDto` nel frontend per usare `moduleCode`.
- [x] Creare migrazione per aggiornare `ModuleCode` nel database per le entità esistenti.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Zentral.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class UpdateAutoCodeModules : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("UPDATE AutoCodes SET ModuleCode = 'warehouse' WHERE EntityCode IN ('warehouse_article', 'warehouse_location', 'inventory_count', 'stock_movement', 'stock_valuation')");
migrationBuilder.Sql("UPDATE AutoCodes SET ModuleCode = 'purchases' WHERE EntityCode IN ('supplier', 'purchase_order')");
migrationBuilder.Sql("UPDATE AutoCodes SET ModuleCode = 'sales' WHERE EntityCode IN ('sales_order')");
migrationBuilder.Sql("UPDATE AutoCodes SET ModuleCode = 'production' WHERE EntityCode IN ('production_order', 'bill_of_materials', 'work_center', 'production_cycle')");
migrationBuilder.Sql("UPDATE AutoCodes SET ModuleCode = 'core' WHERE EntityCode IN ('cliente')");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -30,6 +30,7 @@
"preview": "Preview", "preview": "Preview",
"none": "None", "none": "None",
"view": "View", "view": "View",
"copy": "Copy",
"required": "Required", "required": "Required",
"add": "Add", "add": "Add",
"active": "Active", "active": "Active",
@@ -431,10 +432,20 @@
"patternHelper": "Pattern for code generation", "patternHelper": "Pattern for code generation",
"previewLabel": "Preview:", "previewLabel": "Preview:",
"resetSequence": "Reset Sequence", "resetSequence": "Reset Sequence",
"description": "Description",
"everyYear": "Every year", "everyYear": "Every year",
"everyMonth": "Every month", "everyMonth": "Every month",
"generationActive": "Generation active", "generationActive": "Generation active",
"readOnly": "Code not editable" "readOnly": "Code not editable",
"noConfigs": "No automatic codes configured for this app.",
"modules": {
"core": "Core System",
"warehouse": "Warehouse",
"purchases": "Purchases",
"sales": "Sales",
"production": "Production",
"quality": "Quality"
}
}, },
"customFields": { "customFields": {
"title": "Custom Fields Management", "title": "Custom Fields Management",

View File

@@ -29,7 +29,8 @@
"notes": "Note", "notes": "Note",
"preview": "Anteprima", "preview": "Anteprima",
"none": "Nessuno", "none": "Nessuno",
"view": "Dettaglio" "view": "Dettaglio",
"copy": "Copia"
}, },
"menu": { "menu": {
"dashboard": "Dashboard", "dashboard": "Dashboard",
@@ -428,10 +429,20 @@
"patternHelper": "Pattern per generazione codice", "patternHelper": "Pattern per generazione codice",
"previewLabel": "Anteprima:", "previewLabel": "Anteprima:",
"resetSequence": "Reset Sequenza", "resetSequence": "Reset Sequenza",
"description": "Descrizione",
"everyYear": "Ogni anno", "everyYear": "Ogni anno",
"everyMonth": "Ogni mese", "everyMonth": "Ogni mese",
"generationActive": "Generazione attiva", "generationActive": "Generazione attiva",
"readOnly": "Codice non modificabile" "readOnly": "Codice non modificabile",
"noConfigs": "Nessun codice automatico configurato per questa applicazione.",
"modules": {
"core": "Sistema Base",
"warehouse": "Magazzino",
"purchases": "Acquisti",
"sales": "Vendite",
"production": "Produzione",
"quality": "Qualità"
}
}, },
"customFields": { "customFields": {
"title": "Gestione Campi Personalizzati", "title": "Gestione Campi Personalizzati",

View File

@@ -54,7 +54,7 @@ import type {
AutoCodeUpdateDto, AutoCodeUpdateDto,
PlaceholderInfo, PlaceholderInfo,
} from "../types/autoCode"; } from "../types/autoCode";
import { groupByModule, appNames, appIcons } from "../types/autoCode"; import { groupByModule, appIcons } from "../types/autoCode";
export default function AutoCodesAdminPage() { export default function AutoCodesAdminPage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -169,150 +169,163 @@ export default function AutoCodesAdminPage() {
</Box> </Box>
{/* Accordion per moduli */} {/* Accordion per moduli */}
{Object.entries(groupedConfigs).map(([appCode, moduleConfigs]) => ( {Object.keys(appIcons).map((appCode) => {
<Accordion const moduleConfigs = groupedConfigs[appCode] || [];
key={appCode} return (
expanded={expandedModule === appCode} <Accordion
onChange={(_, isExpanded) => key={appCode}
setExpandedModule(isExpanded ? appCode : false) expanded={expandedModule === appCode}
} onChange={(_, isExpanded) =>
sx={{ mb: 1 }} setExpandedModule(isExpanded ? appCode : false)
> }
<AccordionSummary expandIcon={<ExpandMoreIcon />}> sx={{ mb: 1 }}
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}> >
{getAppIcon(appCode)} <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6"> <Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
{appNames[appCode] || appCode} {getAppIcon(appCode)}
</Typography> <Typography variant="h6">
<Chip {t(`autoCodes.modules.${appCode}`) || appCode}
label={`${moduleConfigs.length} configurazioni`} </Typography>
size="small" <Chip
variant="outlined" label={`${moduleConfigs.length} configurazioni`}
/> size="small"
</Box> variant="outlined"
</AccordionSummary> />
<AccordionDetails> </Box>
<TableContainer component={Paper} variant="outlined"> </AccordionSummary>
<Table size="small"> <AccordionDetails>
<TableHead> {moduleConfigs.length === 0 ? (
<TableRow> <Typography
<TableCell>{t("autoCodes.entity")}</TableCell> color="text.secondary"
<TableCell>{t("autoCodes.prefix")}</TableCell> align="center"
<TableCell>{t("autoCodes.pattern")}</TableCell> sx={{ py: 2 }}
<TableCell>{t("autoCodes.example")}</TableCell> >
<TableCell>{t("autoCodes.sequence")}</TableCell> {t("autoCodes.noConfigs")}
<TableCell>{t("autoCodes.reset")}</TableCell> </Typography>
<TableCell align="center">{t("autoCodes.status")}</TableCell> ) : (
<TableCell align="right">{t("common.actions")}</TableCell> <TableContainer component={Paper} variant="outlined">
</TableRow> <Table size="small">
</TableHead> <TableHead>
<TableBody> <TableRow>
{moduleConfigs.map((config) => ( <TableCell>{t("autoCodes.entity")}</TableCell>
<TableRow key={config.id} hover> <TableCell>{t("autoCodes.prefix")}</TableCell>
<TableCell> <TableCell>{t("autoCodes.pattern")}</TableCell>
<Box> <TableCell>{t("autoCodes.example")}</TableCell>
<Typography variant="body2" fontWeight="medium"> <TableCell>{t("autoCodes.sequence")}</TableCell>
{config.entityName} <TableCell>{t("autoCodes.reset")}</TableCell>
</Typography> <TableCell align="center">{t("autoCodes.status")}</TableCell>
<Typography variant="caption" color="text.secondary"> <TableCell align="right">{t("common.actions")}</TableCell>
{config.entityCode} </TableRow>
</Typography> </TableHead>
</Box> <TableBody>
</TableCell> {moduleConfigs.map((config) => (
<TableCell> <TableRow key={config.id} hover>
<Chip <TableCell>
label={config.prefix || "-"} <Box>
size="small" <Typography variant="body2" fontWeight="medium">
variant="outlined" {config.entityName}
/> </Typography>
</TableCell> <Typography variant="caption" color="text.secondary">
<TableCell> {config.entityCode}
<Typography </Typography>
variant="body2" </Box>
sx={{ fontFamily: "monospace", fontSize: "0.85rem" }} </TableCell>
> <TableCell>
{config.pattern} <Chip
</Typography> label={config.prefix || "-"}
</TableCell>
<TableCell>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 0.5,
}}
>
<Typography
variant="body2"
sx={{
fontFamily: "monospace",
color: "primary.main",
fontWeight: "medium",
}}
>
{config.exampleCode}
</Typography>
<Tooltip title={t("autoCodes.previewTooltip")}>
<IconButton
size="small" size="small"
onClick={() => variant="outlined"
previewMutation.mutate(config.entityCode) />
} </TableCell>
disabled={!config.isEnabled} <TableCell>
<Typography
variant="body2"
sx={{ fontFamily: "monospace", fontSize: "0.85rem" }}
> >
<PreviewIcon fontSize="small" /> {config.pattern}
</IconButton> </Typography>
</Tooltip> </TableCell>
</Box> <TableCell>
</TableCell> <Box
<TableCell> sx={{
<Typography variant="body2"> display: "flex",
{config.lastSequence} alignItems: "center",
</Typography> gap: 0.5,
</TableCell> }}
<TableCell> >
{config.resetSequenceMonthly ? ( <Typography
<Chip label={t("autoCodes.monthly")} size="small" color="info" /> variant="body2"
) : config.resetSequenceYearly ? ( sx={{
<Chip label={t("autoCodes.yearly")} size="small" color="warning" /> fontFamily: "monospace",
) : ( color: "primary.main",
<Chip label={t("autoCodes.never")} size="small" variant="outlined" /> fontWeight: "medium",
)} }}
</TableCell> >
<TableCell align="center"> {config.exampleCode}
<Chip </Typography>
label={config.isEnabled ? t("apps.admin.active") : t("apps.admin.inactive")} <Tooltip title={t("autoCodes.previewTooltip")}>
size="small" <IconButton
color={config.isEnabled ? "success" : "default"} size="small"
/> onClick={() =>
</TableCell> previewMutation.mutate(config.entityCode)
<TableCell align="right"> }
<Tooltip title={t("common.edit")}> disabled={!config.isEnabled}
<IconButton >
size="small" <PreviewIcon fontSize="small" />
onClick={() => setEditingConfig(config)} </IconButton>
> </Tooltip>
<EditIcon fontSize="small" /> </Box>
</IconButton> </TableCell>
</Tooltip> <TableCell>
<Tooltip title={t("autoCodes.resetTooltip")}> <Typography variant="body2">
<IconButton {config.lastSequence}
size="small" </Typography>
onClick={() => setConfirmReset(config.entityCode)} </TableCell>
disabled={!config.isEnabled} <TableCell>
> {config.resetSequenceMonthly ? (
<ResetIcon fontSize="small" /> <Chip label={t("autoCodes.monthly")} size="small" color="info" />
</IconButton> ) : config.resetSequenceYearly ? (
</Tooltip> <Chip label={t("autoCodes.yearly")} size="small" color="warning" />
</TableCell> ) : (
</TableRow> <Chip label={t("autoCodes.never")} size="small" variant="outlined" />
))} )}
</TableBody> </TableCell>
</Table> <TableCell align="center">
</TableContainer> <Chip
</AccordionDetails> label={config.isEnabled ? t("apps.admin.active") : t("apps.admin.inactive")}
</Accordion> size="small"
))} color={config.isEnabled ? "success" : "default"}
/>
</TableCell>
<TableCell align="right">
<Tooltip title={t("common.edit")}>
<IconButton
size="small"
onClick={() => setEditingConfig(config)}
>
<EditIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title={t("autoCodes.resetTooltip")}>
<IconButton
size="small"
onClick={() => setConfirmReset(config.entityCode)}
disabled={!config.isEnabled}
>
<ResetIcon fontSize="small" />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</AccordionDetails>
</Accordion>
);
})}
{/* Dialog modifica configurazione */} {/* Dialog modifica configurazione */}
<EditConfigDialog <EditConfigDialog
@@ -396,7 +409,7 @@ export default function AutoCodesAdminPage() {
> >
{previewCode} {previewCode}
</Typography> </Typography>
<Tooltip title="Copia"> <Tooltip title={t("common.copy")}>
<IconButton <IconButton
size="small" size="small"
onClick={() => { onClick={() => {
@@ -718,7 +731,7 @@ function EditConfigDialog({
<Grid size={{ xs: 12 }}> <Grid size={{ xs: 12 }}>
<TextField <TextField
label="Descrizione" label={t("autoCodes.description")}
value={formData.description ?? config.description ?? ""} value={formData.description ?? config.description ?? ""}
onChange={(e) => onChange={(e) =>
setFormData({ setFormData({

View File

@@ -15,7 +15,7 @@ export interface AutoCodeDto {
lastResetMonth: number | null; lastResetMonth: number | null;
isEnabled: boolean; isEnabled: boolean;
isReadOnly: boolean; isReadOnly: boolean;
appCode: string | null; moduleCode: string | null;
description: string | null; description: string | null;
sortOrder: number; sortOrder: number;
exampleCode: string; exampleCode: string;
@@ -60,7 +60,7 @@ export interface PlaceholderInfo {
*/ */
export function groupByModule(configs: AutoCodeDto[]): Record<string, AutoCodeDto[]> { export function groupByModule(configs: AutoCodeDto[]): Record<string, AutoCodeDto[]> {
return configs.reduce((acc, config) => { return configs.reduce((acc, config) => {
const module = config.appCode || "core"; const module = config.moduleCode || "core";
if (!acc[module]) { if (!acc[module]) {
acc[module] = []; acc[module] = [];
} }
@@ -69,17 +69,7 @@ export function groupByModule(configs: AutoCodeDto[]): Record<string, AutoCodeDt
}, {} as Record<string, AutoCodeDto[]>); }, {} as Record<string, AutoCodeDto[]>);
} }
/**
* Nomi visualizzati per i moduli
*/
export const appNames: Record<string, string> = {
core: "Sistema Base",
warehouse: "Magazzino",
purchases: "Acquisti",
sales: "Vendite",
production: "Produzione",
quality: "Qualità",
};
/** /**
* Icone per i moduli (nomi MUI icons) * Icone per i moduli (nomi MUI icons)