From 8cd79da5eb41ca1fccf5a10aebedb38ad1f2fc72 Mon Sep 17 00:00:00 2001 From: Vincenzo Nikodem Cesqui Podiani Date: Sat, 10 Oct 2026 20:40:24 +0200 Subject: [PATCH] Aggiunta gestione export csv --- .fpm | 4 + export.csv | 4 + include/logic.h | 7 +- include/windowsDeclaration.h | 1 + pm-item-price-default.json | 54 +++++++ pm-item-sold-count-default.json | 14 ++ src/logic.c | 243 +++++++++++++++++++++++++++++--- src/windowsDeclaration.c | 91 +++++++++--- 8 files changed, 376 insertions(+), 42 deletions(-) create mode 100644 .fpm create mode 100644 export.csv create mode 100644 pm-item-price-default.json create mode 100644 pm-item-sold-count-default.json diff --git a/.fpm b/.fpm new file mode 100644 index 0000000..4ea1cf0 --- /dev/null +++ b/.fpm @@ -0,0 +1,4 @@ +{ + "productsFile":".\/pm-item-price-default.json", + "salesFile":".\/pm-item-sold-count-default.json" +} \ No newline at end of file diff --git a/export.csv b/export.csv new file mode 100644 index 0000000..55757a0 --- /dev/null +++ b/export.csv @@ -0,0 +1,4 @@ +Nome;Inventario iniziale;Vendite;Prezzo;Componenti +zainetto;17;0;20.00; +mozzarrella;16;1;3.00; +3x2 mozzarelle;non definito;1;6.00;mozzarrella diff --git a/include/logic.h b/include/logic.h index f99f3cc..c2692ea 100644 --- a/include/logic.h +++ b/include/logic.h @@ -33,13 +33,16 @@ int consumeProductInventory(const char *fname, const char *name, int qty); int getCount(const char *fname, const char *name); void listSold(const char *fname); int addSold(const char *fname, const char *name, int qty); -int updateSold(const char *fname, const char *name, int qty); -int deleteSold(const char *fname, const char *name); +int deleteSold(const char *productsFile, const char *soldFile, const char *name); +int updateSold(const char *productsFile, const char *soldFile, const char *name, int qty); int recordSale(const char *productsFile, const char *soldFile, const char *name, int qty); /* Entrate */ double getTotalRevenue(const char *productsFile, const char *soldFile); +/* Export */ +int exportSalesToCsv(const char *productsFile, const char *soldFile, const char *outFile); + /* Config */ int ensureAppConfig(const char *fname); int loadAppConfig(const char *fname); diff --git a/include/windowsDeclaration.h b/include/windowsDeclaration.h index 7fa7277..dd187d6 100644 --- a/include/windowsDeclaration.h +++ b/include/windowsDeclaration.h @@ -14,6 +14,7 @@ void runAboutWindow(); void runViewSalesWindow(); void runViewProductsWindow(); void runAddProductWindow(); +void runExportCsvWindow(); void example_window(); #endif diff --git a/pm-item-price-default.json b/pm-item-price-default.json new file mode 100644 index 0000000..72f5c27 --- /dev/null +++ b/pm-item-price-default.json @@ -0,0 +1,54 @@ +[ + { + "name":"zainetto", + "price":20.0, + "stock":15 + }, + { + "name":"mozzarrella", + "price":3.0, + "stock":15 + }, + { + "name":"3x2 mozzarelle", + "price":6.0, + "bundle":1, + "stock":-1, + "components":[ + { + "name":"mozzarrella", + "qty":3 + } + ] + }, + { + "name":"astuccio", + "price":10.0, + "stock":-1 + }, + { + "name":"set penne matite", + "price":8.0, + "stock":30 + }, + { + "name":"Scuola", + "price":50.0, + "bundle":1, + "stock":-1, + "components":[ + { + "name":"zainetto", + "qty":1 + }, + { + "name":"astuccio", + "qty":1 + }, + { + "name":"set penne matite", + "qty":2 + } + ] + } +] \ No newline at end of file diff --git a/pm-item-sold-count-default.json b/pm-item-sold-count-default.json new file mode 100644 index 0000000..ce190a5 --- /dev/null +++ b/pm-item-sold-count-default.json @@ -0,0 +1,14 @@ +[ + { + "name":"mozzarrella", + "sold":1 + }, + { + "name":"3x2 mozzarelle", + "sold":1 + }, + { + "name":"Scuola", + "sold":2 + } +] \ No newline at end of file diff --git a/src/logic.c b/src/logic.c index 6023961..fba8ca8 100644 --- a/src/logic.c +++ b/src/logic.c @@ -5,6 +5,8 @@ #include #include "../include/logic.h" +#include + #include "../include/globals.h" #include "../include/jsonFile.h" @@ -39,6 +41,90 @@ static int readInventory(json_object *item, int *stock, int *tracked) { return 0; } +int exportSalesToCsv(const char *productsFile, const char *soldFile, const char *outFile) +{ + json_object *products = getProducts(productsFile); + json_object *sales = getProducts(soldFile); + if (!products || !json_object_is_type(products, json_type_array)) { + if (products) json_object_put(products); + if (sales) json_object_put(sales); + return -1; + } + + FILE *f = fopen(outFile, "w"); + if (!f) { + if (products) json_object_put(products); + if (sales) json_object_put(sales); + return -2; + } + + fprintf(f, "Nome;Inventario iniziale;Vendite;Prezzo;Componenti\n"); + + int n = json_object_array_length(products); + for (int i = 0; i < n; ++i) { + json_object *item = json_object_array_get_idx(products, i); + json_object *jname = NULL; + json_object *jprice = NULL; + json_object *jbundle = NULL; + + const char *name = (json_object_object_get_ex(item, "name", &jname) && jname) ? json_object_get_string(jname) : ""; + double price = (json_object_object_get_ex(item, "price", &jprice) && jprice) ? json_object_get_double(jprice) : 0.0; + int isBundle = (json_object_object_get_ex(item, "bundle", &jbundle) && jbundle) ? json_object_get_int(jbundle) : 0; + + /* sold count */ + int sold = getCount(soldFile, name); + + /* inventory */ + const char *inv_text = NULL; + char inv_buf[64]; + if (isBundle) { + inv_text = "non definito"; + } else { + int stock = -1; + json_object *stock_obj = NULL; + if (json_object_object_get_ex(item, "stock", &stock_obj) && stock_obj) { + stock = json_object_get_int(stock_obj); + } + if (stock < 0) { + inv_text = "non definito"; + } else { + /* initial = current stock + sold */ + snprintf(inv_buf, sizeof(inv_buf), "%d", stock + sold); + inv_text = inv_buf; + } + } + + /* components */ + char comp_buf[512]; + comp_buf[0] = '\0'; + if (isBundle) { + json_object *comp = NULL; + if (json_object_object_get_ex(item, "components", &comp) && comp && json_object_is_type(comp, json_type_array)) { + int m = json_object_array_length(comp); + for (int j = 0; j < m; ++j) { + json_object *c = json_object_array_get_idx(comp, j); + json_object *cjname = NULL; + const char *cname = (json_object_object_get_ex(c, "name", &cjname) && cjname) ? json_object_get_string(cjname) : ""; + if (j) strncat(comp_buf, ";", sizeof(comp_buf)-strlen(comp_buf)-1); + strncat(comp_buf, cname ? cname : "", sizeof(comp_buf)-strlen(comp_buf)-1); + } + } + } + + /* write CSV line; escape semicolons in name/comp if needed (simple) */ + fprintf(f, "%s;%s;%d;%.2f;%s\n", + name ? name : "", + inv_text, + sold, + price, + comp_buf); + } + + fclose(f); + if (products) json_object_put(products); + if (sales) json_object_put(sales); + return 0; +} static void writeInventory(json_object *item, int stock, int tracked) { if (!item) return; @@ -267,6 +353,46 @@ static int consumeTree(json_object *products, json_object *item, int qty) { return 0; } +static int restoreTree(json_object *products, json_object *item, int qty) { + if (!products || !item || qty <= 0) return -1; + + if (!isBundleItem(item)) { + json_object *jstock = NULL; + if (!json_object_object_get_ex(item, "stock", &jstock) || !jstock) + return 0; + int stock = json_object_get_int(jstock); + if (stock < 0) return 0; /* non tracciato */ + json_object_object_add(item, "stock", json_object_new_int(stock + qty)); + return 0; + } + + json_object *components = getBundleComponents(item); + if (!components) return 0; + + size_t count = json_object_array_length(components); + for (size_t i = 0; i < count; i++) { + json_object *c = json_object_array_get_idx(components, i); + json_object *jname = NULL; + if (!json_object_object_get_ex(c, "name", &jname) || !jname) return -1; + json_object *child = findItemByName(products, json_object_get_string(jname)); + if (!child) return 1; + int res = restoreTree(products, child, qty * getComponentQty(c)); + if (res != 0) return res; + } + return 0; +} + +int restoreProductInventory(const char *fname, const char *name, int qty) { + json_object *arr = loadJsonArray(fname); + if (!arr) return -1; + json_object *item = findItemByName(arr, name); + if (!item) { json_object_put(arr); return 1; } + int res = restoreTree(arr, item, qty); + if (res == 0) res = saveJsonArray(fname, arr); + json_object_put(arr); + return res == 0 ? 0 : -1; +} + static char *loadFileText(const char *fname) { FILE *f = fopen(fname, "rb"); long size; @@ -692,38 +818,111 @@ int recordSale(const char *productsFile, const char *soldFile, const char *name, return 0; } -int updateSold(const char *fname, const char *name, int qty) { - json_object *arr = loadJsonArray(fname); - if (!arr) +int deleteSold(const char *productsFile, const char *soldFile, const char *name) { + json_object *sales = loadJsonArray(soldFile); + if (!sales) return -1; - json_object *item = findItemByName(arr, name); + json_object *item = findItemByName(sales, name); if (!item) { - json_object_put(arr); - return 1; + json_object_put(sales); + return 1; /* vendita non presente */ + } + + json_object *jsold = NULL; + int qty = 0; + if (json_object_object_get_ex(item, "sold", &jsold) && jsold) + qty = json_object_get_int(jsold); + + /* backup dei prodotti per il rollback */ + char *backup = NULL; + if (qty > 0) { + backup = loadFileText(productsFile); + if (!backup) { + json_object_put(sales); + return -1; + } + + int res = restoreProductInventory(productsFile, name, qty); + /* 1 = prodotto non più presente: niente da ripristinare, si procede */ + if (res != 0 && res != 1) { + free(backup); + json_object_put(sales); + return res; + } + } + + int res = removeItemByName(sales, name); + if (res == 0) + res = saveJsonArray(soldFile, sales); + json_object_put(sales); + + if (res != 0) { + if (backup) + saveFileText(productsFile, backup); /* rollback inventario */ + free(backup); + return (res < 0) ? res : -1; + } + + free(backup); + return 0; +} + +int updateSold(const char *productsFile, const char *soldFile, const char *name, int qty) { + if (qty < 0) + return -1; + + json_object *sales = loadJsonArray(soldFile); + if (!sales) + return -1; + + json_object *item = findItemByName(sales, name); + if (!item) { + json_object_put(sales); + return 1; /* vendita non presente */ + } + + json_object *jsold = NULL; + int oldQty = 0; + if (json_object_object_get_ex(item, "sold", &jsold) && jsold) + oldQty = json_object_get_int(jsold); + + int delta = qty - oldQty; /* >0: vendute di più, <0: vendute di meno */ + char *backup = NULL; + + if (delta != 0) { + backup = loadFileText(productsFile); + if (!backup) { + json_object_put(sales); + return -1; + } + + int res; + if (delta > 0) + res = consumeProductInventory(productsFile, name, delta); /* controlla anche la disponibilità */ + else + res = restoreProductInventory(productsFile, name, -delta); + + if (res != 0 && !(delta < 0 && res == 1)) { + free(backup); + json_object_put(sales); + return (res == 1) ? -1 : res; /* evita ambiguità con "vendita non presente" */ + } } json_object_object_add(item, "sold", json_object_new_int(qty)); + int res = saveJsonArray(soldFile, sales); + json_object_put(sales); - int res = saveJsonArray(fname, arr); - json_object_put(arr); - return (res == 0) ? 0 : -1; -} - -int deleteSold(const char *fname, const char *name) { - json_object *arr = loadJsonArray(fname); - if (!arr) - return -1; - - int res = removeItemByName(arr, name); if (res != 0) { - json_object_put(arr); - return res; + if (backup) + saveFileText(productsFile, backup); /* rollback inventario */ + free(backup); + return -1; } - res = saveJsonArray(fname, arr); - json_object_put(arr); - return (res == 0) ? 0 : -1; + free(backup); + return 0; } /* ------------------------------------------------------------------ */ diff --git a/src/windowsDeclaration.c b/src/windowsDeclaration.c index 6e898f8..bb71092 100644 --- a/src/windowsDeclaration.c +++ b/src/windowsDeclaration.c @@ -11,6 +11,8 @@ #define MAX_LEN 64 #define LINE_LEN 96 + + /* * Helper */ @@ -29,6 +31,15 @@ static void replaceCommaWithDot(char *text) { *p = '.'; } +static int askAction(const char *title, char **opts, int n_opts) { + WINDOW *win = create_newwin(8, COLS / 3 + 8, LINES / 2 - 4, COLS / 2); + mvwprintw(win, 1, 2, "%s", title); + int choice = uiSelectMenu(win, 3, 2, opts, n_opts); + werase(win); + destroy_win(win); + return choice; +} + static int parseIntValue(const char *text, int *value) { char *end = NULL; long parsed = strtol(text, &end, 10); @@ -216,6 +227,51 @@ static int promptDataFilesForm(char *productsFile, size_t productsFileSize, char return choice; } +static int promptExportFileName(char *outFile, size_t outFileSize) { + WINDOW *win = create_newwin(9, COLS / 2, 0, COLS / 2); + char *opts[] = {"Esporta", "Annulla"}; + + mvwprintw(win, 1, 2, "Esporta vendite a CSV"); + mvwprintw(win, 3, 2, "File di destinazione:"); + if (outFile && outFile[0]) + uiTextInput(win, 4, 2, outFile, (int)outFileSize - 1); + else { + snprintf(outFile, outFileSize, "export.csv"); + uiTextInput(win, 4, 2, outFile, (int)outFileSize - 1); + } + + int choice = uiSelectMenu(win, 6, 4, opts, 2); + werase(win); + destroy_win(win); + return choice; +} + +void runExportCsvWindow() { + char outFile[MAX_LEN + 1]; + snprintf(outFile, sizeof outFile, "export.csv"); + + int choice = promptExportFileName(outFile, sizeof outFile); + if (choice != 1) { + statusf("Operazione annullata"); + return; + } + + char title[128]; + char *opts[] = {"Conferma", "Annulla"}; + snprintf(title, sizeof title, "Esportare le vendite in '%s' ?", outFile); + int ok = askAction(title, opts, 2); + if (ok != 1) { + statusf("Operazione annullata"); + return; + } + + int res = exportSalesToCsv(productPricesFileLocation, countFileLocation, outFile); + if (res == 0) + statusf("Export CSV salvato in '%s'", outFile); + else + warningf("Errore nell'esportazione (codice %d)", res); +} + static char **buildSalesLines(json_object *sales, int *n) { int count = (int)json_object_array_length(sales); char **lines = malloc((count > 0 ? count : 1) * sizeof *lines); @@ -316,15 +372,6 @@ static int listWindow(const char *title, char **lines, int n, const char *footer return result; } -static int askAction(const char *title, char **opts, int n_opts) { - WINDOW *win = create_newwin(8, COLS / 3 + 8, LINES / 2 - 4, COLS / 2); - mvwprintw(win, 1, 2, "%s", title); - int choice = uiSelectMenu(win, 3, 2, opts, n_opts); - werase(win); - destroy_win(win); - return choice; -} - static int confirmDeletion(const char *subject) { char *opts[] = {"Elimina", "Annulla"}; char title[128]; @@ -593,13 +640,16 @@ static void showSaleEditWindow(const char *name, int sold) { return; } - int res = updateSold(countFileLocation, name, qty); + int res = updateSold(productPricesFileLocation, countFileLocation, name, qty); if (res == 0) statusf("Vendita aggiornata: '%s' = %d", name, qty); else if (res == 1) warningf("La vendita '%s' non esiste piu'.", name); + else if (res == 2) + warningf("Stock insufficiente per portare '%s' a %d vendite.", name, qty); else - warningf("Errore nella scrittura del file '%s'.", countFileLocation); + warningf("Errore nell'aggiornamento di vendite/inventario ('%s', '%s').", + countFileLocation, productPricesFileLocation); } static void deleteSaleByName(const char *name) { @@ -608,13 +658,14 @@ static void deleteSaleByName(const char *name) { return; } - int res = deleteSold(countFileLocation, name); + int res = deleteSold(productPricesFileLocation, countFileLocation, name); if (res == 0) - statusf("Vendita eliminata: '%s'", name); + statusf("Vendita eliminata: '%s' (inventario ripristinato)", name); else if (res == 1) warningf("La vendita '%s' non esiste piu'.", name); else - warningf("Errore nella scrittura del file '%s'.", countFileLocation); + warningf("Errore nell'aggiornamento di vendite/inventario ('%s', '%s').", + countFileLocation, productPricesFileLocation); } static void showSaleActionWindow(const char *name, int sold) { @@ -826,6 +877,9 @@ static void runMainMenuChoice(int choice) { runOptionsMenu(); break; case 7: + runExportCsvWindow(); + break; + case 8: runAboutWindow(); break; } @@ -836,9 +890,10 @@ int runMainMenu() { "Segna vendita", "Vedi vendite", "Vedi prodotti", - "Aggiungi prodotti", - "Crea nuovo bundle", + "Aggiungi nuovo prodotto", + "Aggiungi nuovo bundle", "Opzioni", + "Esporta CSV", "About", "Exit" }; @@ -861,11 +916,11 @@ int runMainMenu() { clrtoeol(); refresh(); - if (choice >= 1 && choice <= 7) + if (choice >= 1 && choice <= 8) runMainMenuChoice(choice); destroy_win(mainWin); - return choice != 8; + return choice != 9; } void runAboutWindow() {