From c6543cc583fa1be2911d1e193db9d0337a015b5f Mon Sep 17 00:00:00 2001 From: Vincenzo Nikodem Cesqui Podiani Date: Sat, 10 Oct 2026 19:18:28 +0200 Subject: [PATCH] Modifiche e correzioni generali ai assisted --- .fpm | 4 + include/globals.h | 1 + include/jsonFile.h | 17 + include/logic.h | 24 +- include/windowsDeclaration.h | 1 + pm-item-price-default.json | 29 + pm-item-sold-count-default.json | 6 + src/globals.c | 1 + src/jsonFile.c | 113 ++++ src/logic.c | 703 ++++++++++++++++++--- src/main.c | 28 +- src/windowsBack.c | 1 + src/windowsDeclaration.c | 1038 +++++++++++++++++++++++++------ 13 files changed, 1645 insertions(+), 321 deletions(-) create mode 100644 .fpm create mode 100644 include/jsonFile.h create mode 100644 pm-item-price-default.json create mode 100644 pm-item-sold-count-default.json create mode 100644 src/jsonFile.c 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/include/globals.h b/include/globals.h index 85afab9..47616ff 100644 --- a/include/globals.h +++ b/include/globals.h @@ -7,5 +7,6 @@ extern char productPricesFileLocation[64]; // Dichiarazione extern extern char countFileLocation[64]; +extern char configFileLocation[64]; #endif diff --git a/include/jsonFile.h b/include/jsonFile.h new file mode 100644 index 0000000..94fe1aa --- /dev/null +++ b/include/jsonFile.h @@ -0,0 +1,17 @@ +#ifndef JSONFILE_H +#define JSONFILE_H + +#include + +int existsFile(const char *fname); +int createFile(const char *fname); +int renameDataFile(const char *oldName, const char *newName); + +json_object *loadJsonArray(const char *fname); +int saveJsonArray(const char *fname, json_object *arr); + +json_object *findItemByName(json_object *arr, const char *name); +int removeItemByName(json_object *arr, const char *name); +int renameItemByName(json_object *arr, const char *oldName, const char *newName); + +#endif \ No newline at end of file diff --git a/include/logic.h b/include/logic.h index b916721..f99f3cc 100644 --- a/include/logic.h +++ b/include/logic.h @@ -6,7 +6,9 @@ #include #include #include -#include +#include "jsonFile.h" + +#define PRODUCT_STOCK_UNLIMITED (-1) struct product { float cost; @@ -14,23 +16,33 @@ struct product { char name; }; -/* File */ -int existsFile(const char *fname); -int createFile(const char *fname); -int renameDataFile(const char *oldName, const char *newName); - /* Prodotti */ json_object *getProducts(const char *fname); void listProducts(const char *fname); int addProduct(const char *fname, const char *name, double price); +int addProductWithInventory(const char *fname, const char *name, double price, int stock); +int addBundleProduct(const char *fname, const char *name, double price, json_object *components); double getPrice(const char *fname, const char *name); +int updateProduct(const char *productsFile, const char *soldFile, const char *oldName, const char *newName, double price); +int updateProductWithInventory(const char *productsFile, const char *soldFile, const char *oldName, const char *newName, double price, int stock); +int getProductInventory(const char *fname, const char *name, int *stock, int *tracked); +int setProductInventory(const char *fname, const char *name, int stock, int tracked); +int consumeProductInventory(const char *fname, const char *name, int qty); /* Copie vendute */ 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 recordSale(const char *productsFile, const char *soldFile, const char *name, int qty); /* Entrate */ double getTotalRevenue(const char *productsFile, const char *soldFile); +/* Config */ +int ensureAppConfig(const char *fname); +int loadAppConfig(const char *fname); +int saveAppConfig(const char *fname); + #endif diff --git a/include/windowsDeclaration.h b/include/windowsDeclaration.h index f1e4ec6..7fa7277 100644 --- a/include/windowsDeclaration.h +++ b/include/windowsDeclaration.h @@ -9,6 +9,7 @@ int runMainMenu(); void runOptionsMenu(); void runSellWindow(); +void runCreateBundleWindow(); void runAboutWindow(); void runViewSalesWindow(); void runViewProductsWindow(); diff --git a/pm-item-price-default.json b/pm-item-price-default.json new file mode 100644 index 0000000..4e4bfc5 --- /dev/null +++ b/pm-item-price-default.json @@ -0,0 +1,29 @@ +[ + { + "name":"Lilla", + "price":4000.0, + "stock":1 + }, + { + "name":"Carciofo", + "price":0.94999999999999996, + "stock":15 + }, + { + "name":"Mozzarelline ine ine", + "price":2.0, + "stock":-1 + }, + { + "name":"Parulli", + "price":1.0, + "bundle":1, + "stock":-1, + "components":[ + { + "name":"Mozzarelline ine ine", + "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..563489d --- /dev/null +++ b/pm-item-sold-count-default.json @@ -0,0 +1,6 @@ +[ + { + "name":"Parulli", + "sold":1 + } +] \ No newline at end of file diff --git a/src/globals.c b/src/globals.c index f0130d9..88e1396 100644 --- a/src/globals.c +++ b/src/globals.c @@ -2,4 +2,5 @@ char productPricesFileLocation[64]="./pm-item-price-default.json"; char countFileLocation[64]="./pm-item-sold-count-default.json"; +char configFileLocation[64]=".fpm"; diff --git a/src/jsonFile.c b/src/jsonFile.c new file mode 100644 index 0000000..5eec3e0 --- /dev/null +++ b/src/jsonFile.c @@ -0,0 +1,113 @@ +#include +#include +#include + +#include + +#include "../include/jsonFile.h" + +int existsFile(const char *fname) { + FILE *file = fopen(fname, "r"); + if (file) { + fclose(file); + return 1; + } + return 0; +} + +int createFile(const char *fname) { + json_object *arr = json_object_new_array(); + if (!arr) + return -1; + + int res = json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY); + json_object_put(arr); + return (res == 0) ? 0 : -1; +} + +int renameDataFile(const char *oldName, const char *newName) { + return (rename(oldName, newName) == 0) ? 0 : -1; +} + +static json_object *loadRawArray(const char *fname) { + if (!existsFile(fname) && createFile(fname) != 0) + return NULL; + + FILE *f = fopen(fname, "r"); + if (!f) + return NULL; + + int empty = (fgetc(f) == EOF); + fclose(f); + if (empty) + return json_object_new_array(); + + json_object *arr = json_object_from_file(fname); + if (!arr) + return NULL; + + if (!json_object_is_type(arr, json_type_array)) { + json_object_put(arr); + return NULL; + } + + return arr; +} + +json_object *loadJsonArray(const char *fname) { + return loadRawArray(fname); +} + +int saveJsonArray(const char *fname, json_object *arr) { + if (!arr) + return -1; + return (json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY) == 0) ? 0 : -1; +} + +json_object *findItemByName(json_object *arr, const char *name) { + if (!arr || !name) + return NULL; + + size_t n = json_object_array_length(arr); + for (size_t i = 0; i < n; i++) { + json_object *item = json_object_array_get_idx(arr, i); + json_object *jname = NULL; + + if (json_object_object_get_ex(item, "name", &jname) && + strcmp(json_object_get_string(jname), name) == 0) + return item; + } + + return NULL; +} + +int removeItemByName(json_object *arr, const char *name) { + if (!arr || !name) + return -1; + + size_t n = json_object_array_length(arr); + for (size_t i = 0; i < n; i++) { + json_object *item = json_object_array_get_idx(arr, i); + json_object *jname = NULL; + + if (json_object_object_get_ex(item, "name", &jname) && + strcmp(json_object_get_string(jname), name) == 0) { + json_object_array_del_idx(arr, (int)i, 1); + return 0; + } + } + + return 1; +} + +int renameItemByName(json_object *arr, const char *oldName, const char *newName) { + if (!arr || !oldName || !newName) + return -1; + + json_object *item = findItemByName(arr, oldName); + if (!item) + return 1; + + json_object_object_add(item, "name", json_object_new_string(newName)); + return 0; +} \ No newline at end of file diff --git a/src/logic.c b/src/logic.c index 2fc1eda..6023961 100644 --- a/src/logic.c +++ b/src/logic.c @@ -6,11 +6,327 @@ #include "../include/logic.h" #include "../include/globals.h" +#include "../include/jsonFile.h" + +static int isBundleItem(json_object *item); + +static int readInventory(json_object *item, int *stock, int *tracked) { + json_object *jstock = NULL; + + if (!item || !stock || !tracked) + return -1; + + if (isBundleItem(item)) { + *stock = PRODUCT_STOCK_UNLIMITED; + *tracked = 0; + return 0; + } + + if (!json_object_object_get_ex(item, "stock", &jstock) || !jstock) { + *stock = PRODUCT_STOCK_UNLIMITED; + *tracked = 0; + return 0; + } + + *stock = json_object_get_int(jstock); + if (*stock < 0) { + *stock = PRODUCT_STOCK_UNLIMITED; + *tracked = 0; + } else { + *tracked = 1; + } + + return 0; +} + +static void writeInventory(json_object *item, int stock, int tracked) { + if (!item) + return; + + json_object_object_add(item, "stock", + json_object_new_int(tracked ? stock : PRODUCT_STOCK_UNLIMITED)); +} + +static int loadProductItem(const char *fname, const char *name, json_object **arr_out, json_object **item_out) { + json_object *arr = loadJsonArray(fname); + if (!arr) + return -1; + + json_object *item = findItemByName(arr, name); + if (!item) { + json_object_put(arr); + return 1; + } + + *arr_out = arr; + *item_out = item; + return 0; +} + +static json_object *buildAppConfigObject(void) { + json_object *obj = json_object_new_object(); + if (!obj) + return NULL; + + json_object_object_add(obj, "productsFile", json_object_new_string(productPricesFileLocation)); + json_object_object_add(obj, "salesFile", json_object_new_string(countFileLocation)); + return obj; +} + +static int writeAppConfigObject(const char *fname, json_object *obj) { + if (!obj) + return -1; + return (json_object_to_file_ext(fname, obj, JSON_C_TO_STRING_PRETTY) == 0) ? 0 : -1; +} + +static int readConfigString(json_object *obj, const char *key, char *out, size_t outSize) { + json_object *v = NULL; + const char *s = NULL; + + if (!obj || !key || !out || outSize == 0) + return 0; + + if (json_object_object_get_ex(obj, key, &v) && v) + s = json_object_get_string(v); + if (!s || s[0] == '\0') + return 0; + + snprintf(out, outSize, "%s", s); + return 1; +} + +static int isBundleItem(json_object *item) { + json_object *v = NULL; + return item && json_object_object_get_ex(item, "bundle", &v) && json_object_get_boolean(v); +} + +static int getComponentQty(json_object *component) { + json_object *v = NULL; + + if (!component) + return 1; + + if (!json_object_object_get_ex(component, "qty", &v) || !v) + return 1; + + return json_object_get_int(v); +} + +static json_object *getBundleComponents(json_object *bundleItem) { + json_object *components = NULL; + + if (!bundleItem) + return NULL; + + if (!json_object_object_get_ex(bundleItem, "components", &components) || !components) + return NULL; + + if (!json_object_is_type(components, json_type_array)) + return NULL; + + return components; +} + +static const char *bundleTagText(json_object *item) { + return isBundleItem(item) ? " [BUNDLE]" : ""; +} + +static void formatProductLabel(json_object *item, char *buf, size_t size) { + json_object *v = NULL; + int tracked = 0; + int stock = PRODUCT_STOCK_UNLIMITED; + + if (!buf || size == 0) + return; + + if (isBundleItem(item)) { + snprintf(buf, size, "X"); + return; + } + + if (json_object_object_get_ex(item, "stock", &v) && v) { + stock = json_object_get_int(v); + tracked = stock >= 0; + } + + if (tracked) + snprintf(buf, size, "%d", stock); + else + snprintf(buf, size, "X"); +} + +static json_object *findItemByNameExcluding(json_object *arr, const char *name, json_object *exclude) { + if (!arr || !name) + return NULL; + + size_t n = json_object_array_length(arr); + for (size_t i = 0; i < n; i++) { + json_object *item = json_object_array_get_idx(arr, i); + json_object *jname = NULL; + + if (item == exclude) + continue; + + if (json_object_object_get_ex(item, "name", &jname) && + strcmp(json_object_get_string(jname), name) == 0) + return item; + } + + return NULL; +} + +static int canConsumeTree(json_object *products, json_object *item, int qty) { + if (!products || !item || qty <= 0) + return -1; + + if (!isBundleItem(item)) { + json_object *jstock = NULL; + int stock = PRODUCT_STOCK_UNLIMITED; + + if (!json_object_object_get_ex(item, "stock", &jstock) || !jstock) + return 0; + + stock = json_object_get_int(jstock); + if (stock < 0) + return 0; + + return (stock >= qty) ? 0 : 2; + } + + 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 *component = json_object_array_get_idx(components, i); + json_object *jname = NULL; + json_object *child = NULL; + int componentQty = getComponentQty(component); + + if (componentQty <= 0) + return -1; + + if (!json_object_object_get_ex(component, "name", &jname) || !jname) + return -1; + + child = findItemByName(products, json_object_get_string(jname)); + if (!child) + return 1; + + int res = canConsumeTree(products, child, qty * componentQty); + if (res != 0) + return res; + } + + return 0; +} + +static int consumeTree(json_object *products, json_object *item, int qty) { + if (!products || !item || qty <= 0) + return -1; + + if (!isBundleItem(item)) { + json_object *jstock = NULL; + int stock = PRODUCT_STOCK_UNLIMITED; + + if (!json_object_object_get_ex(item, "stock", &jstock) || !jstock) + return 0; + + stock = json_object_get_int(jstock); + if (stock < 0) + return 0; + + 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 *component = json_object_array_get_idx(components, i); + json_object *jname = NULL; + json_object *child = NULL; + int componentQty = getComponentQty(component); + + if (!json_object_object_get_ex(component, "name", &jname) || !jname) + return -1; + + child = findItemByName(products, json_object_get_string(jname)); + if (!child) + return 1; + + int res = consumeTree(products, child, qty * componentQty); + if (res != 0) + return res; + } + + return 0; +} + +static char *loadFileText(const char *fname) { + FILE *f = fopen(fname, "rb"); + long size; + char *buf; + + if (!f) + return NULL; + + if (fseek(f, 0, SEEK_END) != 0) { + fclose(f); + return NULL; + } + + size = ftell(f); + if (size < 0) { + fclose(f); + return NULL; + } + + rewind(f); + buf = malloc((size_t)size + 1); + if (!buf) { + fclose(f); + return NULL; + } + + if (size > 0 && fread(buf, 1, (size_t)size, f) != (size_t)size) { + free(buf); + fclose(f); + return NULL; + } + + buf[size] = '\0'; + fclose(f); + return buf; +} + +static int saveFileText(const char *fname, const char *text) { + FILE *f = fopen(fname, "wb"); + size_t len; + + if (!f) + return -1; + + len = strlen(text); + if (len > 0 && fwrite(text, 1, len, f) != len) { + fclose(f); + return -1; + } + + fclose(f); + return 0; +} /* * Formato dei file (JSON): * - * File prodotti: [ {"name": "Maglietta S", "price": 19.90}, ... ] + * File prodotti: [ {"name": "Maglietta S", "price": 19.90, "stock": 12}, ... ] + * stock = numero intero, oppure -1 per inventario infinito + * bundle = 1, components = [ {"name": "...", "qty": 2}, ... ] * File vendite: [ {"name": "Maglietta S", "sold": 12}, ... ] * * Compilazione: aggiungere -ljson-c (e -lncurses) al linker. @@ -20,84 +336,6 @@ * e.g. persona vuole acquistare maglietta S ma non c'è la taglia */ -/* ------------------------------------------------------------------ */ -/* Utilità sui file */ -/* ------------------------------------------------------------------ */ - -int existsFile(const char *fname) { - FILE *file = fopen(fname, "r"); - if (file) { - fclose(file); - return 1; - } - return 0; -} - -/* Crea un file JSON contenente un array vuoto. Ritorna 0 se ok, -1 se errore. */ -int createFile(const char *fname) { - json_object *arr = json_object_new_array(); - if (!arr) - return -1; - - int res = json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY); - json_object_put(arr); - return (res == 0) ? 0 : -1; -} - -/* Cambia il nome di un file di dati. Ritorna 0 se ok, -1 se errore. */ -int renameDataFile(const char *oldName, const char *newName) { - return (rename(oldName, newName) == 0) ? 0 : -1; -} - -/* ------------------------------------------------------------------ */ -/* Funzioni interne */ -/* ------------------------------------------------------------------ */ - -/* - * Carica l'array JSON dal file (creandolo se non esiste). - * Il chiamante deve liberare il risultato con json_object_put(). - * Ritorna NULL in caso di errore o se il contenuto non è un array. - */ -static json_object *loadArray(const char *fname) { - if (!existsFile(fname) && createFile(fname) != 0) - return NULL; - - /* Un file vuoto (e.g. creato a mano o dalla vecchia createFile) - * non e' JSON valido: lo trattiamo come un array vuoto. */ - FILE *f = fopen(fname, "r"); - if (!f) - return NULL; - int empty = (fgetc(f) == EOF); - fclose(f); - if (empty) - return json_object_new_array(); - - json_object *arr = json_object_from_file(fname); - if (!arr) - return NULL; - - if (!json_object_is_type(arr, json_type_array)) { - json_object_put(arr); - return NULL; - } - return arr; -} - -/* Cerca un elemento per nome. Ritorna l'oggetto (non va liberato) o NULL. */ -static json_object *findByName(json_object *arr, const char *name) { - size_t n = json_object_array_length(arr); - - for (size_t i = 0; i < n; i++) { - json_object *item = json_object_array_get_idx(arr, i); - json_object *jname; - - if (json_object_object_get_ex(item, "name", &jname) && - strcmp(json_object_get_string(jname), name) == 0) - return item; - } - return NULL; -} - /* ------------------------------------------------------------------ */ /* Prodotti */ /* ------------------------------------------------------------------ */ @@ -107,12 +345,12 @@ static json_object *findByName(json_object *arr, const char *name) { * oppure NULL in caso di errore. */ json_object *getProducts(const char *fname) { - return loadArray(fname); + return loadJsonArray(fname); } /* Stampa a schermo (ncurses) la lista dei prodotti con il relativo prezzo. */ void listProducts(const char *fname) { - json_object *arr = loadArray(fname); + json_object *arr = loadJsonArray(fname); if (!arr) { printw("Errore nella lettura del file %s\n", fname); refresh(); @@ -126,12 +364,19 @@ void listProducts(const char *fname) { for (size_t i = 0; i < n; i++) { json_object *item = json_object_array_get_idx(arr, i); json_object *jname, *jprice; + char stockBuf[16]; if (json_object_object_get_ex(item, "name", &jname) && json_object_object_get_ex(item, "price", &jprice)) - printw("%zu) %-30s %8.2f\n", i + 1, + formatProductLabel(item, stockBuf, sizeof stockBuf); + + if (json_object_object_get_ex(item, "name", &jname) && + json_object_object_get_ex(item, "price", &jprice)) + printw("%zu) %-30s %8.2f [%s]%s\n", i + 1, json_object_get_string(jname), - json_object_get_double(jprice)); + json_object_get_double(jprice), + stockBuf, + bundleTagText(item)); } refresh(); @@ -143,11 +388,15 @@ void listProducts(const char *fname) { * Ritorna 0 se ok, -1 se errore, 1 se il prodotto esiste già. */ int addProduct(const char *fname, const char *name, double price) { - json_object *arr = loadArray(fname); + return addProductWithInventory(fname, name, price, PRODUCT_STOCK_UNLIMITED); +} + +int addProductWithInventory(const char *fname, const char *name, double price, int stock) { + json_object *arr = loadJsonArray(fname); if (!arr) return -1; - if (findByName(arr, name)) { + if (findItemByName(arr, name)) { json_object_put(arr); return 1; } @@ -155,21 +404,50 @@ int addProduct(const char *fname, const char *name, double price) { json_object *item = json_object_new_object(); json_object_object_add(item, "name", json_object_new_string(name)); json_object_object_add(item, "price", json_object_new_double(price)); + writeInventory(item, stock, stock != PRODUCT_STOCK_UNLIMITED); json_object_array_add(arr, item); - int res = json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY); + int res = saveJsonArray(fname, arr); + json_object_put(arr); + return (res == 0) ? 0 : -1; +} + +int addBundleProduct(const char *fname, const char *name, double price, json_object *components) { + json_object *arr = loadJsonArray(fname); + if (!arr) + return -1; + + if (findItemByName(arr, name)) { + json_object_put(arr); + return 1; + } + + if (!components || !json_object_is_type(components, json_type_array) || json_object_array_length(components) == 0) { + json_object_put(arr); + return -1; + } + + json_object *item = json_object_new_object(); + json_object_object_add(item, "name", json_object_new_string(name)); + json_object_object_add(item, "price", json_object_new_double(price)); + json_object_object_add(item, "bundle", json_object_new_int(1)); + json_object_object_add(item, "stock", json_object_new_int(PRODUCT_STOCK_UNLIMITED)); + json_object_object_add(item, "components", components); + json_object_array_add(arr, item); + + int res = saveJsonArray(fname, arr); json_object_put(arr); return (res == 0) ? 0 : -1; } /* Ritorna il prezzo di un prodotto, oppure -1.0 se non trovato/errore. */ double getPrice(const char *fname, const char *name) { - json_object *arr = loadArray(fname); + json_object *arr = loadJsonArray(fname); if (!arr) return -1.0; double price = -1.0; - json_object *item = findByName(arr, name); + json_object *item = findItemByName(arr, name); json_object *jprice; if (item && json_object_object_get_ex(item, "price", &jprice)) @@ -179,18 +457,142 @@ double getPrice(const char *fname, const char *name) { return price; } +int updateProduct(const char *productsFile, const char *soldFile, const char *oldName, const char *newName, double price) { + json_object *products = NULL; + json_object *item = NULL; + int stock = PRODUCT_STOCK_UNLIMITED; + int tracked = 0; + + if (loadProductItem(productsFile, oldName, &products, &item) != 0) + return -1; + + readInventory(item, &stock, &tracked); + json_object_put(products); + + return updateProductWithInventory(productsFile, soldFile, oldName, newName, price, + tracked ? stock : PRODUCT_STOCK_UNLIMITED); +} + +int updateProductWithInventory(const char *productsFile, const char *soldFile, const char *oldName, const char *newName, double price, int stock) { + json_object *products = loadJsonArray(productsFile); + if (!products) + return -1; + + json_object *item = findItemByName(products, oldName); + if (!item) { + json_object_put(products); + return 1; + } + + if (strcmp(oldName, newName) != 0 && findItemByNameExcluding(products, newName, item)) { + json_object_put(products); + return 2; + } + + json_object_object_add(item, "name", json_object_new_string(newName)); + json_object_object_add(item, "price", json_object_new_double(price)); + writeInventory(item, stock, stock != PRODUCT_STOCK_UNLIMITED); + + if (saveJsonArray(productsFile, products) != 0) { + json_object_put(products); + return -1; + } + json_object_put(products); + + if (strcmp(oldName, newName) != 0) { + json_object *sales = loadJsonArray(soldFile); + if (!sales) + return -1; + + if (renameItemByName(sales, oldName, newName) != 0) { + json_object_put(sales); + return 1; + } + + if (saveJsonArray(soldFile, sales) != 0) { + json_object_put(sales); + return -1; + } + json_object_put(sales); + } + + return 0; +} + +int getProductInventory(const char *fname, const char *name, int *stock, int *tracked) { + 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 = readInventory(item, stock, tracked); + json_object_put(arr); + return res; +} + +int setProductInventory(const char *fname, const char *name, int stock, int tracked) { + json_object *arr = loadJsonArray(fname); + if (!arr) + return -1; + + json_object *item = findItemByName(arr, name); + if (!item) { + json_object_put(arr); + return 1; + } + + writeInventory(item, stock, tracked); + + int res = saveJsonArray(fname, arr); + json_object_put(arr); + return (res == 0) ? 0 : -1; +} + +int consumeProductInventory(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 check = canConsumeTree(arr, item, qty); + if (check != 0) { + json_object_put(arr); + return check; + } + + int res = consumeTree(arr, item, qty); + if (res != 0) { + json_object_put(arr); + return res; + } + + res = saveJsonArray(fname, arr); + json_object_put(arr); + return (res == 0) ? 0 : -1; +} + /* ------------------------------------------------------------------ */ /* Copie vendute */ /* ------------------------------------------------------------------ */ /* Ritorna le copie vendute di un prodotto (0 se non presente, -1 se errore). */ int getCount(const char *fname, const char *name) { - json_object *arr = loadArray(fname); + json_object *arr = loadJsonArray(fname); if (!arr) return -1; int count = 0; - json_object *item = findByName(arr, name); + json_object *item = findItemByName(arr, name); json_object *jsold; if (item && json_object_object_get_ex(item, "sold", &jsold)) @@ -202,7 +604,7 @@ int getCount(const char *fname, const char *name) { /* Stampa a schermo (ncurses) le copie vendute di ogni prodotto. */ void listSold(const char *fname) { - json_object *arr = loadArray(fname); + json_object *arr = loadJsonArray(fname); if (!arr) { printw("Errore nella lettura del file %s\n", fname); refresh(); @@ -234,11 +636,11 @@ void listSold(const char *fname) { * Ritorna 0 se ok, -1 se errore. */ int addSold(const char *fname, const char *name, int qty) { - json_object *arr = loadArray(fname); + json_object *arr = loadJsonArray(fname); if (!arr) return -1; - json_object *item = findByName(arr, name); + json_object *item = findItemByName(arr, name); if (item) { json_object *jsold; @@ -255,7 +657,71 @@ int addSold(const char *fname, const char *name, int qty) { json_object_array_add(arr, item); } - int res = json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY); + int res = saveJsonArray(fname, arr); + json_object_put(arr); + return (res == 0) ? 0 : -1; +} + +int recordSale(const char *productsFile, const char *soldFile, const char *name, int qty) { + char *backup = NULL; + int consumeRes; + + if (qty <= 0) + return -1; + + if (!existsFile(productsFile) && createFile(productsFile) != 0) + return -1; + + backup = loadFileText(productsFile); + if (!backup) + return -1; + + consumeRes = consumeProductInventory(productsFile, name, qty); + if (consumeRes != 0) { + free(backup); + return consumeRes; + } + + if (addSold(soldFile, name, qty) != 0) { + saveFileText(productsFile, backup); + free(backup); + return -1; + } + + free(backup); + return 0; +} + +int updateSold(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; + } + + json_object_object_add(item, "sold", json_object_new_int(qty)); + + 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; + } + + res = saveJsonArray(fname, arr); json_object_put(arr); return (res == 0) ? 0 : -1; } @@ -269,11 +735,11 @@ int addSold(const char *fname, const char *name, int qty) { * per ogni prodotto. Ritorna -1.0 in caso di errore. */ double getTotalRevenue(const char *productsFile, const char *soldFile) { - json_object *products = loadArray(productsFile); + json_object *products = loadJsonArray(productsFile); if (!products) return -1.0; - json_object *sales = loadArray(soldFile); + json_object *sales = loadJsonArray(soldFile); if (!sales) { json_object_put(products); return -1.0; @@ -290,7 +756,7 @@ double getTotalRevenue(const char *productsFile, const char *soldFile) { !json_object_object_get_ex(sale, "sold", &jsold)) continue; - json_object *prod = findByName(products, json_object_get_string(jname)); + json_object *prod = findItemByName(products, json_object_get_string(jname)); if (prod && json_object_object_get_ex(prod, "price", &jprice)) total += json_object_get_double(jprice) * json_object_get_int(jsold); } @@ -299,3 +765,46 @@ double getTotalRevenue(const char *productsFile, const char *soldFile) { json_object_put(sales); return total; } + +int saveAppConfig(const char *fname) { + json_object *obj = buildAppConfigObject(); + if (!obj) + return -1; + + int res = writeAppConfigObject(fname, obj); + json_object_put(obj); + return res; +} + +int ensureAppConfig(const char *fname) { + if (!existsFile(fname)) + return saveAppConfig(fname); + return 0; +} + +int loadAppConfig(const char *fname) { + json_object *obj = NULL; + json_object *loaded = NULL; + + if (ensureAppConfig(fname) != 0) + return -1; + + loaded = json_object_from_file(fname); + if (!loaded || !json_object_is_type(loaded, json_type_object)) { + if (loaded) + json_object_put(loaded); + return saveAppConfig(fname); + } + + readConfigString(loaded, "productsFile", productPricesFileLocation, sizeof productPricesFileLocation); + readConfigString(loaded, "salesFile", countFileLocation, sizeof countFileLocation); + + obj = buildAppConfigObject(); + if (!obj) { + json_object_put(loaded); + return -1; + } + json_object_put(obj); + json_object_put(loaded); + return 0; +} diff --git a/src/main.c b/src/main.c index 80630f7..9993b7a 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,7 @@ #include "../include/windowsDeclaration.h" #include "../include/windowsBack.h" #include "../include/logic.h" +#include "../include/globals.h" void printLogoBG(); @@ -43,36 +44,15 @@ int main(){ noecho(); curs_set(0); raw(); + if (loadAppConfig(configFileLocation) != 0) + warningf("Impossibile caricare la config '%s', uso i valori di default.", configFileLocation); printLogoBG(); statusf(" "); refresh(); - - int running = 0; - while(running != 6){ + while (runMainMenu()) { printLogoBG(); refresh(); - running = runMainMenu(); - switch(running) { - case 1: - runSellWindow(); - break; - case 2: - runViewSalesWindow(); - break; - case 3: - runViewProductsWindow(); - break; - case 4: - runAddProductWindow(); - break; - case 5: - runAboutWindow(); -// runOptionsMenu(); - break; - case 6: - break; - } } endwin(); diff --git a/src/windowsBack.c b/src/windowsBack.c index a87a9f1..c390324 100644 --- a/src/windowsBack.c +++ b/src/windowsBack.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "../include/windowsBack.h" diff --git a/src/windowsDeclaration.c b/src/windowsDeclaration.c index 971b657..6e898f8 100644 --- a/src/windowsDeclaration.c +++ b/src/windowsDeclaration.c @@ -7,24 +7,68 @@ #include "../include/windowsBack.h" #include "../include/logic.h" #include "../include/globals.h" + #define MAX_LEN 64 #define LINE_LEN 96 /* - * - * HELPER - * - * + * Helper */ - static void freeLines(char **lines, int n) { + if (!lines) + return; for (int i = 0; i < n; i++) free(lines[i]); free(lines); } - -// Legge un campo stringa da un elemento JSON ("?" se manca) + +static void replaceCommaWithDot(char *text) { + for (char *p = text; p && *p; p++) + if (*p == ',') + *p = '.'; +} + +static int parseIntValue(const char *text, int *value) { + char *end = NULL; + long parsed = strtol(text, &end, 10); + if (text[0] == '\0' || !end || *end != '\0') + return 0; + *value = (int)parsed; + return 1; +} + +static int parseDoubleValue(const char *text, double *value) { + char *end = NULL; + double parsed = strtod(text, &end); + if (text[0] == '\0' || !end || *end != '\0') + return 0; + *value = parsed; + return 1; +} + +static int parseInventoryValue(const char *text, int *tracked, int *stock) { + char *end = NULL; + long parsed; + + if (!text || !tracked || !stock) + return 0; + + if (strcmp(text, "*") == 0) { + *tracked = 0; + *stock = PRODUCT_STOCK_UNLIMITED; + return 1; + } + + parsed = strtol(text, &end, 10); + if (text[0] == '\0' || !end || *end != '\0' || parsed < 0) + return 0; + + *tracked = 1; + *stock = (int)parsed; + return 1; +} + static const char *jsonStr(json_object *item, const char *key) { json_object *v = NULL; const char *s = NULL; @@ -32,19 +76,76 @@ static const char *jsonStr(json_object *item, const char *key) { s = json_object_get_string(v); return s ? s : "?"; } - + static double jsonDouble(json_object *item, const char *key) { json_object *v = NULL; return (json_object_object_get_ex(item, key, &v) && v) ? json_object_get_double(v) : 0.0; } - + static int jsonInt(json_object *item, const char *key) { json_object *v = NULL; return (json_object_object_get_ex(item, key, &v) && v) ? json_object_get_int(v) : 0; } - -// Costruisce le righe "nome prezzo" dall'array dei prodotti. -// *n contiene il numero di righe create. Ritorna NULL se malloc fallisce. + +static int productIsBundle(json_object *item); + +static void jsonInventoryText(json_object *item, char *buf, size_t size) { + json_object *v = NULL; + if (!buf || size == 0) + return; + + if (productIsBundle(item)) { + snprintf(buf, size, "X"); + return; + } + + if (!json_object_object_get_ex(item, "stock", &v) || !v) { + snprintf(buf, size, "X"); + return; + } + + int stock = json_object_get_int(v); + if (stock < 0) + snprintf(buf, size, "X"); + else + snprintf(buf, size, "%d", stock); +} + +static void jsonInventoryEditText(json_object *item, char *buf, size_t size) { + json_object *v = NULL; + + if (!buf || size == 0) + return; + + if (productIsBundle(item)) { + snprintf(buf, size, "*"); + return; + } + + if (!json_object_object_get_ex(item, "stock", &v) || !v) { + snprintf(buf, size, "*"); + return; + } + + int stock = json_object_get_int(v); + if (stock < 0) + snprintf(buf, size, "*"); + else + snprintf(buf, size, "%d", stock); +} + +static int productIsBundle(json_object *item) { + json_object *v = NULL; + return item && json_object_object_get_ex(item, "bundle", &v) && json_object_get_boolean(v); +} + +static int nameInList(const char **names, int count, const char *name) { + for (int i = 0; i < count; i++) + if (strcmp(names[i], name) == 0) + return 1; + return 0; +} + static char **buildProductLines(json_object *products, int *n) { int count = (int)json_object_array_length(products); char **lines = malloc((count > 0 ? count : 1) * sizeof *lines); @@ -52,47 +153,123 @@ static char **buildProductLines(json_object *products, int *n) { *n = 0; return NULL; } - + int made = 0; for (int i = 0; i < count; i++) { json_object *item = json_object_array_get_idx(products, i); + char stockBuf[16]; + const char *tag = productIsBundle(item) ? " [BUNDLE]" : ""; lines[i] = malloc(LINE_LEN); if (!lines[i]) break; - snprintf(lines[i], LINE_LEN, "%-26.26s %8.2f", - jsonStr(item, "name"), jsonDouble(item, "price")); + jsonInventoryText(item, stockBuf, sizeof stockBuf); + snprintf(lines[i], LINE_LEN, "%-22.22s %8.2f %8s%s", jsonStr(item, "name"), jsonDouble(item, "price"), stockBuf, tag); made++; } *n = made; return lines; } - + +static json_object *buildBundleCandidates(json_object *products, const char **excludedNames, int excludedCount) { + json_object *filtered = json_object_new_array(); + if (!filtered) + return NULL; + + int count = (int)json_object_array_length(products); + for (int i = 0; i < count; i++) { + json_object *item = json_object_array_get_idx(products, i); + json_object *jname = NULL; + + if (productIsBundle(item)) + continue; + if (!json_object_object_get_ex(item, "name", &jname) || !jname) + continue; + if (nameInList(excludedNames, excludedCount, json_object_get_string(jname))) + continue; + + json_object_array_add(filtered, json_object_get(item)); + } + + return filtered; +} + +static int parsePositiveIntValue(const char *text, int *value) { + if (!parseIntValue(text, value) || *value <= 0) + return 0; + return 1; +} + +static int promptDataFilesForm(char *productsFile, size_t productsFileSize, char *salesFile, size_t salesFileSize) { + WINDOW *win = create_newwin(LINES / 2 + 2, COLS / 2, 0, COLS / 2); + char *opts[] = {"Salva", "Annulla"}; + + mvwprintw(win, 1, 2, "Impostazioni file dati"); + mvwprintw(win, 3, 2, "File prodotti:"); + uiTextInput(win, 4, 2, productsFile, (int)productsFileSize - 1); + + mvwprintw(win, 6, 2, "File vendite:"); + uiTextInput(win, 7, 2, salesFile, (int)salesFileSize - 1); + + int choice = uiSelectMenu(win, 9, 4, opts, 2); + werase(win); + destroy_win(win); + return choice; +} + +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); + if (!lines) { + *n = 0; + return NULL; + } + + int made = 0; + for (int i = 0; i < count; i++) { + json_object *item = json_object_array_get_idx(sales, i); + const char *name = jsonStr(item, "name"); + int sold = jsonInt(item, "sold"); + double price = getPrice(productPricesFileLocation, name); + + lines[i] = malloc(LINE_LEN); + if (!lines[i]) + break; + + if (price >= 0) + snprintf(lines[i], LINE_LEN, "%-18.18s %4d %9.2f", name, sold, sold * price); + else + snprintf(lines[i], LINE_LEN, "%-18.18s %4d %9s", name, sold, "n/d"); + made++; + } + + *n = made; + return lines; +} + /* * Finestra a elenco scorrevole. * - selectable = 1: ritorna l'indice scelto con INVIO/SPAZIO, -1 se annullato (q) * - selectable = 0: sola lettura, ritorna sempre -1 * "footer" (puo' essere NULL) e' una riga di testo fissa sopra i comandi. */ -static int listWindow(const char *title, char **lines, int n, - const char *footer, int selectable) { - int height = LINES - 4; - int width = COLS / 2; +static int listWindow(const char *title, char **lines, int n, const char *footer, int selectable) { + int height = LINES - 2; + int width = COLS / 2; int visible = height - 6; - int textw = width - 4; + int textw = width - 4; if (visible < 1) visible = 1; - if (textw < 0) textw = 0; - + if (textw < 0) textw = 0; + WINDOW *win = create_newwin(height, width, 0, COLS / 2); keypad(win, TRUE); mvwprintw(win, 1, 2, "%.*s", textw, title); - + int sel = 0, top = 0, result = -1, done = 0; - + while (!done) { - // Mantiene la selezione dentro l'area visibile if (sel < top) top = sel; if (sel >= top + visible) top = sel - visible + 1; - + for (int i = 0; i < visible; i++) { mvwhline(win, 3 + i, 1, ' ', width - 2); int idx = top + i; @@ -103,17 +280,15 @@ static int listWindow(const char *title, char **lines, int n, } if (n == 0) mvwprintw(win, 3, 2, "Nessun elemento."); - + mvwhline(win, height - 3, 1, ' ', width - 2); if (footer) mvwprintw(win, height - 3, 2, "%.*s", textw, footer); - + mvwhline(win, height - 2, 1, ' ', width - 2); - mvwprintw(win, height - 2, 2, "%.*s", textw, - selectable ? "SU/GIU scorri INVIO scegli q annulla" - : "SU/GIU scorri q chiudi"); + mvwprintw(win, height - 2, 2, "%.*s", textw, selectable ? "SU/GIU scorri INVIO scegli q annulla" : "SU/GIU scorri q chiudi"); wrefresh(win); - + switch (wgetch(win)) { case KEY_UP: case 'k': if (sel > 0) sel--; break; case KEY_DOWN: case 'j': if (sel < n - 1) sel++; break; @@ -135,24 +310,477 @@ static int listWindow(const char *title, char **lines, int n, break; } } - + werase(win); destroy_win(win); 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]; + snprintf(title, sizeof title, "Eliminare %s?", subject); + return askAction(title, opts, 2) == 1; +} + +static int promptProductForm(const char *title, const char *currentName, double currentPrice, const char *currentStockText, char *name, size_t nameSize, char *priceStr, size_t priceSize, char *stockStr, size_t stockSize) { + WINDOW *win = create_newwin(LINES / 2, COLS / 2, 0, COLS / 2); + char *opts[] = {"Salva", "Annulla"}; + + mvwprintw(win, 1, 2, "%s", title); + mvwprintw(win, 3, 2, "Nome:"); + if (currentName) + snprintf(name, nameSize, "%s", currentName); + else + name[0] = '\0'; + uiTextInput(win, 4, 2, name, (int)nameSize - 1); + + mvwprintw(win, 6, 2, "Prezzo (es. 19.90):"); + if (currentPrice >= 0.0) + snprintf(priceStr, priceSize, "%.2f", currentPrice); + else + priceStr[0] = '\0'; + uiTextInput(win, 7, 2, priceStr, (int)priceSize - 1); + + mvwprintw(win, 9, 2, "Inventario (* = infinito):"); + if (currentStockText) + snprintf(stockStr, stockSize, "%s", currentStockText); + else + snprintf(stockStr, stockSize, "*"); + uiTextInput(win, 10, 2, stockStr, (int)stockSize - 1); + + int choice = uiSelectMenu(win, 12, 4, opts, 2); + werase(win); + destroy_win(win); + return choice; +} + +static int promptSaleQtyForm(const char *title, const char *productName, int currentQty, char *qtyStr, size_t qtySize) { + WINDOW *win = create_newwin(10, COLS / 2, 0, COLS / 2); + char *opts[] = {"Salva", "Annulla"}; + + mvwprintw(win, 1, 2, "%s", title); + mvwprintw(win, 3, 2, "Prodotto: %.30s", productName); + mvwprintw(win, 4, 2, "Quantita' venduta:"); + if (currentQty > 0) + snprintf(qtyStr, qtySize, "%d", currentQty); + else + qtyStr[0] = '\0'; + uiTextInput(win, 5, 2, qtyStr, (int)qtySize - 1); + + int choice = uiSelectMenu(win, 7, 4, opts, 2); + werase(win); + destroy_win(win); + return choice; +} + +static int promptBundleHeaderForm(char *name, size_t nameSize, char *priceStr, size_t priceSize, char *countStr, size_t countSize) { + WINDOW *win = create_newwin(LINES / 2, COLS / 2, 0, COLS / 2); + char *opts[] = {"Salva", "Annulla"}; + + mvwprintw(win, 1, 2, "Crea nuovo bundle"); + mvwprintw(win, 3, 2, "Nome bundle:"); + name[0] = '\0'; + uiTextInput(win, 4, 2, name, (int)nameSize - 1); + + mvwprintw(win, 6, 2, "Prezzo bundle:"); + priceStr[0] = '\0'; + uiTextInput(win, 7, 2, priceStr, (int)priceSize - 1); + + mvwprintw(win, 9, 2, "Numero componenti:"); + countStr[0] = '\0'; + uiTextInput(win, 10, 2, countStr, (int)countSize - 1); + + int choice = uiSelectMenu(win, 12, 4, opts, 2); + werase(win); + destroy_win(win); + return choice; +} + +static int promptBundleComponentQty(const char *componentName, char *qtyStr, size_t qtySize) { + WINDOW *win = create_newwin(9, COLS / 2, 0, COLS / 2); + char *opts[] = {"Salva", "Annulla"}; + + mvwprintw(win, 1, 2, "Componente bundle"); + mvwprintw(win, 3, 2, "Prodotto: %.30s", componentName); + mvwprintw(win, 4, 2, "Quantita' richiesta:"); + qtyStr[0] = '\0'; + uiTextInput(win, 5, 2, qtyStr, (int)qtySize - 1); + + int choice = uiSelectMenu(win, 6, 4, opts, 2); + werase(win); + destroy_win(win); + return choice; +} + +static int chooseBundleComponent(json_object *products, const char **excludedNames, int excludedCount, char *selectedName, size_t selectedNameSize, int *qtyOut) { + json_object *filtered = buildBundleCandidates(products, excludedNames, excludedCount); + if (!filtered) + return -1; + + int n = 0; + char **lines = buildProductLines(filtered, &n); + if (!lines) { + json_object_put(filtered); + return -1; + } + + int idx = listWindow("Scegli componente bundle", lines, n, "q annulla", 1); + if (idx < 0) { + freeLines(lines, n); + json_object_put(filtered); + return 1; + } + + json_object *item = json_object_array_get_idx(filtered, idx); + const char *name = jsonStr(item, "name"); + char qtyStr[12]; + int choice = promptBundleComponentQty(name, qtyStr, sizeof qtyStr); + if (choice != 1) { + freeLines(lines, n); + json_object_put(filtered); + return 1; + } + + int qty = 0; + if (!parsePositiveIntValue(qtyStr, &qty)) { + freeLines(lines, n); + json_object_put(filtered); + warningf("La quantita' '%s' non e' valida.", qtyStr); + return -1; + } + + snprintf(selectedName, selectedNameSize, "%s", name); + *qtyOut = qty; + + freeLines(lines, n); + json_object_put(filtered); + return 0; +} + +static char **buildBundleComponentLines(json_object *bundleItem, int *n) { + json_object *components = NULL; + + if (!bundleItem || !n) { + if (n) + *n = 0; + return NULL; + } + + if (!json_object_object_get_ex(bundleItem, "components", &components) || !components || + !json_object_is_type(components, json_type_array)) { + *n = 0; + return NULL; + } + + int count = (int)json_object_array_length(components); + char **lines = malloc((count > 0 ? count : 1) * sizeof *lines); + if (!lines) { + *n = 0; + return NULL; + } + + int made = 0; + for (int i = 0; i < count; i++) { + json_object *component = json_object_array_get_idx(components, i); + json_object *jname = NULL; + json_object *jqty = NULL; + + lines[i] = malloc(LINE_LEN); + if (!lines[i]) + break; + + if (json_object_object_get_ex(component, "name", &jname) && + json_object_object_get_ex(component, "qty", &jqty)) { + snprintf(lines[i], LINE_LEN, "%-26.26s x%6d", + json_object_get_string(jname), + json_object_get_int(jqty)); + } else { + snprintf(lines[i], LINE_LEN, "Componente non valido"); + } + made++; + } + + *n = made; + return lines; +} + +static void showBundleDetailsWindow(const char *title, double price, json_object *bundleItem) { + int n = 0; + char **lines = buildBundleComponentLines(bundleItem, &n); + char footer[96]; + + snprintf(footer, sizeof footer, "Bundle [BUNDLE] Prezzo: %.2f Componenti: %d", price, n); + + if (!lines) { + char *fallback[] = {"Impossibile leggere i componenti del bundle."}; + listWindow(title, fallback, 1, footer, 0); + return; + } + + listWindow(title, lines, n, footer, 0); + freeLines(lines, n); +} + +static void showProductEditWindow(const char *oldName, double oldPrice, const char *oldStockText, int isBundle) { + char name[33]; + char priceStr[13]; + char stockStr[13]; + + int choice = promptProductForm("Modifica prodotto", oldName, oldPrice, oldStockText, name, sizeof(name), priceStr, sizeof(priceStr), stockStr, sizeof(stockStr)); + if (choice != 1) { + statusf("Operazione annullata"); + return; + } + + replaceCommaWithDot(priceStr); + + double price; + if (name[0] == '\0') { + warningf("Il nome del prodotto non puo' essere vuoto."); + return; + } + if (!parseDoubleValue(priceStr, &price) || price < 0.0) { + warningf("Il prezzo '%s' non e' valido.", priceStr); + return; + } + + int tracked = 0; + int stock = PRODUCT_STOCK_UNLIMITED; + if (isBundle) { + if (strcmp(stockStr, "*") != 0) + warningf("I bundle non hanno inventario proprio: il valore viene ignorato."); + tracked = 0; + stock = PRODUCT_STOCK_UNLIMITED; + } else if (!parseInventoryValue(stockStr, &tracked, &stock)) { + warningf("L'inventario '%s' non e' valido. Usa un numero oppure '*'.", stockStr); + return; + } + + int res = updateProductWithInventory(productPricesFileLocation, countFileLocation, oldName, name, price, + tracked ? stock : PRODUCT_STOCK_UNLIMITED); + if (res == 0) + statusf("Prodotto modificato: '%s' -> '%s'", oldName, name); + else if (res == 2) + warningf("Esiste gia' un prodotto chiamato '%s'.", name); + else if (res == 1) + warningf("Il prodotto '%s' non esiste piu'.", oldName); + else + warningf("Errore nella scrittura del file '%s'.", productPricesFileLocation); +} + +static void showSaleEditWindow(const char *name, int sold) { + char qtyStr[12]; + + int choice = promptSaleQtyForm("Modifica vendita", name, sold, qtyStr, sizeof(qtyStr)); + if (choice != 1) { + statusf("Operazione annullata"); + return; + } + + int qty; + if (!parseIntValue(qtyStr, &qty) || qty <= 0) { + warningf("La quantita' '%s' non e' valida.", qtyStr); + return; + } + + int res = updateSold(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 + warningf("Errore nella scrittura del file '%s'.", countFileLocation); +} + +static void deleteSaleByName(const char *name) { + if (!confirmDeletion(name)) { + statusf("Operazione annullata"); + return; + } + + int res = deleteSold(countFileLocation, name); + if (res == 0) + statusf("Vendita eliminata: '%s'", name); + else if (res == 1) + warningf("La vendita '%s' non esiste piu'.", name); + else + warningf("Errore nella scrittura del file '%s'.", countFileLocation); +} + +static void showSaleActionWindow(const char *name, int sold) { + char *opts[] = {"Modifica", "Elimina", "Indietro"}; + char title[128]; + snprintf(title, sizeof title, "Vendita: %.30s (%d)", name, sold); + + int choice = askAction(title, opts, 3); + if (choice == 1) + showSaleEditWindow(name, sold); + else if (choice == 2) + deleteSaleByName(name); +} + +static void showProductActionWindow(const char *name, double price, const char *stockText, const char *stockEditText, int isBundle, json_object *item) { + char title[128]; + snprintf(title, sizeof title, "Prodotto: %.30s (%.2f, stock %s)%s", + name, price, stockText ? stockText : "non definito", isBundle ? " [BUNDLE]" : ""); + + if (isBundle) { + showBundleDetailsWindow(title, price, item); + return; + } + + char *opts[] = {"Modifica", "Indietro"}; + int choice = askAction(title, opts, 2); + if (choice == 1) + showProductEditWindow(name, price, stockEditText ? stockEditText : "*", isBundle); +} + +static void showProductAddWindow(void) { + char name[33] = ""; + char priceStr[13] = ""; + char stockStr[13] = "*"; + + int choice = promptProductForm("Aggiungi un nuovo prodotto", NULL, -1.0, "*", name, sizeof(name), priceStr, sizeof(priceStr), stockStr, sizeof(stockStr)); + if (choice != 1) { + statusf("Operazione annullata"); + return; + } + + replaceCommaWithDot(priceStr); + + double price; + if (name[0] == '\0') { + warningf("Il nome del prodotto non puo' essere vuoto."); + return; + } + if (!parseDoubleValue(priceStr, &price) || price < 0.0) { + warningf("Il prezzo '%s' non e' valido.", priceStr); + return; + } + + int tracked = 0; + int stock = PRODUCT_STOCK_UNLIMITED; + if (!parseInventoryValue(stockStr, &tracked, &stock)) { + warningf("L'inventario '%s' non e' valido. Usa un numero oppure '*'.", stockStr); + return; + } + + int res = addProductWithInventory(productPricesFileLocation, name, price, tracked ? stock : PRODUCT_STOCK_UNLIMITED); + if (res == 0) + statusf("Prodotto '%s' aggiunto a %.2f", name, price); + else if (res == 1) + warningf("Il prodotto '%s' esiste gia'.", name); + else + warningf("Errore nella scrittura del file '%s'.", productPricesFileLocation); +} + +static void showCreateBundleWindow(void) { + char name[33]; + char priceStr[13]; + char countStr[8]; + const int maxComponents = 32; + + int choice = promptBundleHeaderForm(name, sizeof(name), priceStr, sizeof(priceStr), countStr, sizeof(countStr)); + if (choice != 1) { + statusf("Operazione annullata"); + return; + } + + replaceCommaWithDot(priceStr); + + double price; + int componentCount; + if (name[0] == '\0') { + warningf("Il nome del bundle non puo' essere vuoto."); + return; + } + if (!parseDoubleValue(priceStr, &price) || price < 0.0) { + warningf("Il prezzo '%s' non e' valido.", priceStr); + return; + } + if (!parsePositiveIntValue(countStr, &componentCount) || componentCount > maxComponents) { + warningf("Il numero di componenti '%s' non e' valido.", countStr); + return; + } + + json_object *products = getProducts(productPricesFileLocation); + if (!products) { + warningf("Impossibile leggere il file '%s'.", productPricesFileLocation); + return; + } + + char selectedNames[maxComponents][33]; + int selectedQtys[maxComponents]; + int selectedCount = 0; + + for (int i = 0; i < componentCount; i++) { + char componentName[33]; + int qty = 0; + const char *excluded[maxComponents]; + for (int j = 0; j < selectedCount; j++) + excluded[j] = selectedNames[j]; + + int res = chooseBundleComponent(products, excluded, selectedCount, componentName, sizeof(componentName), &qty); + if (res != 0) { + if (res < 0) + warningf("Errore nella selezione dei componenti."); + else + statusf("Operazione annullata"); + json_object_put(products); + return; + } + + snprintf(selectedNames[selectedCount], sizeof selectedNames[selectedCount], "%s", componentName); + selectedQtys[selectedCount] = qty; + selectedCount++; + statusf("Componente %d/%d: %s x%d", selectedCount, componentCount, componentName, qty); + } + + json_object *components = json_object_new_array(); + if (!components) { + warningf("Memoria insufficiente."); + json_object_put(products); + return; + } + + for (int i = 0; i < selectedCount; i++) { + json_object *component = json_object_new_object(); + json_object_object_add(component, "name", json_object_new_string(selectedNames[i])); + json_object_object_add(component, "qty", json_object_new_int(selectedQtys[i])); + json_object_array_add(components, component); + } + + int res = addBundleProduct(productPricesFileLocation, name, price, components); + if (res == 0) + statusf("Bundle '%s' creato a %.2f", name, price); + else if (res == 1) { + json_object_put(components); + warningf("Il prodotto '%s' esiste gia'.", name); + } else { + json_object_put(components); + warningf("Errore nella scrittura del file '%s'.", productPricesFileLocation); + } + + json_object_put(products); +} /* - * * Finestre - * - * - */ + */ void example_window() { WINDOW *win; - int height = LINES / 2 -2; + int height = LINES / 2 - 2; int width = COLS / 2; int starty = LINES / 2; int startx = COLS / 2; @@ -177,14 +805,42 @@ void example_window() { destroy_win(win); } -int runMainMenu(){ +static void runMainMenuChoice(int choice) { + switch (choice) { + case 1: + runSellWindow(); + break; + case 2: + runViewSalesWindow(); + break; + case 3: + runViewProductsWindow(); + break; + case 4: + runAddProductWindow(); + break; + case 5: + runCreateBundleWindow(); + break; + case 6: + runOptionsMenu(); + break; + case 7: + runAboutWindow(); + break; + } +} + +int runMainMenu() { char *mainMenu[] = { - "Segna vendita", - "Vedi vendite", - "Vedi prodotti", - "Aggiungi prodotti", - "About", - "Exit" + "Segna vendita", + "Vedi vendite", + "Vedi prodotti", + "Aggiungi prodotti", + "Crea nuovo bundle", + "Opzioni", + "About", + "Exit" }; int n_mainMenu = sizeof(mainMenu) / sizeof(char *); @@ -199,18 +855,22 @@ int runMainMenu(){ refresh(); int choice = uiSelectMenu(mainWin, height / 2 - 3, width / 2 - 9, mainMenu, n_mainMenu); - statusf("Avvio opzione '%s'", mainMenu[choice - 1]); + if (choice >= 1 && choice <= n_mainMenu) + statusf("Avvio opzione '%s'", mainMenu[choice - 1]); clrtoeol(); refresh(); + if (choice >= 1 && choice <= 7) + runMainMenuChoice(choice); + destroy_win(mainWin); - return choice; + return choice != 8; } -void runAboutWindow(){ +void runAboutWindow() { WINDOW *aboutWin; - aboutWin = create_newwin(LINES/2-2, COLS/2, LINES/2, 0); + aboutWin = create_newwin(LINES / 2 - 2, COLS / 2, LINES / 2, 0); wwrap_print(aboutWin, 1, 1, "Questo e' un software fatto per Roma2LUG, il codice verra' pubblicato sul gitea del lug in futuro. La parte logica e' fatta con Claude Code, non avevo voglia di scrivere quei pezzi e volevo effettivamente rendere utile il codice che avevo scritto per gestire ncurses. Oh well, spero vi possa essere comunque utile. Buon lavoro / Divertimento!"); statusf("Uovo di pasqua ()"); @@ -221,89 +881,58 @@ void runAboutWindow(){ } void runAddProductWindow() { - WINDOW *addWin = create_newwin(LINES / 2, COLS / 2, 0, COLS / 2); - - char name[33] = ""; - char priceStr[13] = ""; - char *opts[] = {"Salva", "Annulla"}; - - mvwprintw(addWin, 1, 2, "Aggiungi un nuovo prodotto"); - - mvwprintw(addWin, 3, 2, "Nome:"); - uiTextInput(addWin, 4, 2, name, 32); - - mvwprintw(addWin, 6, 2, "Prezzo (es. 19.90):"); - uiTextInput(addWin, 7, 2, priceStr, 12); - - int choice = uiSelectMenu(addWin, 9, 4, opts, 2); - - if (choice == 1) { - for (char *p = priceStr; *p; p++) - if (*p == ',') *p = '.'; - - char *end; - double price = strtod(priceStr, &end); - - if (name[0] == '\0') { - warningf("Il nome del prodotto non puo' essere vuoto."); - } else if (priceStr[0] == '\0' || *end != '\0' || price < 0) { - warningf("Il prezzo '%s' non e' valido.", priceStr); - } else { - int res = addProduct(productPricesFileLocation, name, price); - if (res == 0) - statusf("Prodotto '%s' aggiunto a %.2f", name, price); - else if (res == 1) - warningf("Il prodotto '%s' esiste gia'.", name); - else - warningf("Errore nella scrittura del file '%s'.", productPricesFileLocation); - } - } else { - statusf("Operazione annullata"); - } - - werase(addWin); - destroy_win(addWin); + showProductAddWindow(); } -void runOptionsMenu(){ - WINDOW *optionsWin; - optionsWin = create_newwin(LINES/2, COLS/2, 0, COLS/2); +void runCreateBundleWindow() { + showCreateBundleWindow(); +} - char input[MAX_LEN + 1]; - strcpy(input, productPricesFileLocation); +void runOptionsMenu() { + char productsFile[MAX_LEN + 1]; + char salesFile[MAX_LEN + 1]; - mvwprintw(optionsWin, 1, 2, "Posizione file prodotti:"); - uiTextInput(optionsWin, 2, 2, input, MAX_LEN); + strcpy(productsFile, productPricesFileLocation); + strcpy(salesFile, countFileLocation); - if (existsFile(input)) { - statusf("Il file '%s' esiste e verra' utilizzato al posto di '%s'", input, productPricesFileLocation); - strcpy(productPricesFileLocation, input); - } else { - warningf("Il file '%s' non esiste, utilizzo il vecchio file '%s'", input, productPricesFileLocation); + int choice = promptDataFilesForm(productsFile, sizeof productsFile, salesFile, sizeof salesFile); + if (choice != 1) { + statusf("Operazione annullata"); + return; } - werase(optionsWin); - destroy_win(optionsWin); + if (productsFile[0] == '\0' || salesFile[0] == '\0') { + warningf("I percorsi dei file non possono essere vuoti."); + return; + } + + strcpy(productPricesFileLocation, productsFile); + strcpy(countFileLocation, salesFile); + + if (saveAppConfig(configFileLocation) == 0) { + statusf("Config salvata in '%s' | Prodotti: '%s' | Vendite: '%s'", configFileLocation, productPricesFileLocation, countFileLocation); + } else { + warningf("Impossibile salvare la config '%s'.", configFileLocation); + } } /* ------------------------------------------------------------------ */ /* Segna vendita */ /* ------------------------------------------------------------------ */ - + void runSellWindow() { json_object *products = getProducts(productPricesFileLocation); if (!products) { warningf("Impossibile leggere il file '%s'.", productPricesFileLocation); return; } - + if (json_object_array_length(products) == 0) { warningf("Non ci sono prodotti. Aggiungine uno da 'Gestisci prodotti'."); json_object_put(products); return; } - - // 1. Scelta del prodotto + int n; char **lines = buildProductLines(products, &n); if (!lines) { @@ -311,130 +940,151 @@ void runSellWindow() { json_object_put(products); return; } - + int idx = listWindow("Segna vendita - scegli il prodotto", lines, n, NULL, 1); freeLines(lines, n); - + if (idx < 0) { statusf("Operazione annullata"); json_object_put(products); return; } - + json_object *item = json_object_array_get_idx(products, idx); - const char *name = jsonStr(item, "name"); - double price = jsonDouble(item, "price"); - - // 2. Quantita' venduta + const char *name = jsonStr(item, "name"); + double price = jsonDouble(item, "price"); + WINDOW *win = create_newwin(9, COLS / 2, 0, COLS / 2); keypad(win, TRUE); - + char qtyStr[8] = "1"; - char *opts[] = {"Salva", "Annulla"}; - + char *opts[] = {"Salva", "Annulla"}; + mvwprintw(win, 1, 2, "Segna vendita"); mvwprintw(win, 3, 2, "Prodotto: %.30s", name); mvwprintw(win, 4, 2, "Quantita' venduta:"); uiTextInput(win, 5, 2, qtyStr, 6); - + int choice = uiSelectMenu(win, 6, 4, opts, 2); - + if (choice == 1) { - char *end; - long qty = strtol(qtyStr, &end, 10); - - if (qtyStr[0] == '\0' || *end != '\0' || qty <= 0 || qty > 1000000) { + int qty; + if (!parseIntValue(qtyStr, &qty) || qty <= 0 || qty > 1000000) { warningf("La quantita' '%s' non e' valida.", qtyStr); - } else if (addSold(countFileLocation, name, (int)qty) == 0) { - statusf("Registrata vendita: %ld x '%s' = %.2f", qty, name, qty * price); } else { - warningf("Errore nella scrittura del file '%s'.", countFileLocation); + int res = recordSale(productPricesFileLocation, countFileLocation, name, qty); + if (res == 0) { + int stock = 0; + int tracked = 0; + if (getProductInventory(productPricesFileLocation, name, &stock, &tracked) == 0 && tracked) + statusf("Registrata vendita: %d x '%s' = %.2f (rimasti: %d)", qty, name, qty * price, stock); + else + statusf("Registrata vendita: %d x '%s' = %.2f", qty, name, qty * price); + } else if (res == 1) { + warningf("Il prodotto '%s' non esiste piu'.", name); + } else if (res == 2) { + warningf("Scorta insufficiente per '%s'.", name); + } else { + warningf("Errore nella scrittura del file '%s'.", countFileLocation); + } } } else { statusf("Operazione annullata"); } - + werase(win); destroy_win(win); json_object_put(products); } - + /* ------------------------------------------------------------------ */ /* Vedi vendite */ /* ------------------------------------------------------------------ */ - + void runViewSalesWindow() { - json_object *sales = getProducts(countFileLocation); - if (!sales) { - warningf("Impossibile leggere il file '%s'.", countFileLocation); - return; - } - - int count = (int)json_object_array_length(sales); - char **lines = malloc((count > 0 ? count : 1) * sizeof *lines); - if (!lines) { - warningf("Memoria insufficiente."); - json_object_put(sales); - return; - } - - int n = 0; - for (int i = 0; i < count; i++) { - json_object *item = json_object_array_get_idx(sales, i); - const char *name = jsonStr(item, "name"); - int sold = jsonInt(item, "sold"); - double price = getPrice(productPricesFileLocation, name); - - lines[i] = malloc(LINE_LEN); - if (!lines[i]) - break; - - if (price >= 0) - snprintf(lines[i], LINE_LEN, "%-18.18s %4d %9.2f", name, sold, sold * price); + while (1) { + json_object *sales = getProducts(countFileLocation); + if (!sales) { + warningf("Impossibile leggere il file '%s'.", countFileLocation); + return; + } + + int n; + char **lines = buildSalesLines(sales, &n); + if (!lines) { + warningf("Memoria insufficiente."); + json_object_put(sales); + return; + } + + char footer[64]; + double total = getTotalRevenue(productPricesFileLocation, countFileLocation); + if (total >= 0) + snprintf(footer, sizeof footer, "Entrate totali: %.2f", total); else - snprintf(lines[i], LINE_LEN, "%-18.18s %4d %9s", name, sold, "n/d"); - n++; + snprintf(footer, sizeof footer, "Entrate totali: n/d"); + + int idx = listWindow("Vendite (prodotto / copie / incasso)", lines, n, footer, 1); + if (idx < 0) { + freeLines(lines, n); + json_object_put(sales); + return; + } + + json_object *item = json_object_array_get_idx(sales, idx); + const char *name = jsonStr(item, "name"); + int sold = jsonInt(item, "sold"); + + freeLines(lines, n); + json_object_put(sales); + + showSaleActionWindow(name, sold); } - - char footer[64]; - double total = getTotalRevenue(productPricesFileLocation, countFileLocation); - if (total >= 0) - snprintf(footer, sizeof footer, "Entrate totali: %.2f", total); - else - snprintf(footer, sizeof footer, "Entrate totali: n/d"); - - listWindow("Vendite (prodotto / copie / incasso)", lines, n, footer, 0); - - freeLines(lines, n); - json_object_put(sales); } - + /* ------------------------------------------------------------------ */ /* Vedi prodotti */ /* ------------------------------------------------------------------ */ - + void runViewProductsWindow() { - json_object *products = getProducts(productPricesFileLocation); - if (!products) { - warningf("Impossibile leggere il file '%s'.", productPricesFileLocation); - return; - } - - int n; - char **lines = buildProductLines(products, &n); - if (!lines) { - warningf("Memoria insufficiente."); + while (1) { + json_object *products = getProducts(productPricesFileLocation); + if (!products) { + warningf("Impossibile leggere il file '%s'.", productPricesFileLocation); + return; + } + + int n; + char **lines = buildProductLines(products, &n); + if (!lines) { + warningf("Memoria insufficiente."); + json_object_put(products); + return; + } + + char footer[64]; + snprintf(footer, sizeof footer, "Prodotti in listino: %d (* = infinito)", n); + + int idx = listWindow("Prodotti (nome / prezzo / stock)", lines, n, footer, 1); + if (idx < 0) { + freeLines(lines, n); + json_object_put(products); + return; + } + + json_object *item = json_object_array_get_idx(products, idx); + const char *name = jsonStr(item, "name"); + double price = jsonDouble(item, "price"); + char stockText[16]; + char stockEditText[16]; + int isBundle = productIsBundle(item); + jsonInventoryText(item, stockText, sizeof stockText); + jsonInventoryEditText(item, stockEditText, sizeof stockEditText); + + freeLines(lines, n); + showProductActionWindow(name, price, stockText, stockEditText, isBundle, item); json_object_put(products); - return; } - - char footer[64]; - snprintf(footer, sizeof footer, "Prodotti in listino: %d", n); - - listWindow("Prodotti (nome / prezzo)", lines, n, footer, 0); - - freeLines(lines, n); - json_object_put(products); }