Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c088caf03e | ||
|
|
8cd79da5eb |
No files matched your search
@@ -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
|
||||
|
+5
-2
@@ -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);
|
||||
|
||||
@@ -14,6 +14,7 @@ void runAboutWindow();
|
||||
void runViewSalesWindow();
|
||||
void runViewProductsWindow();
|
||||
void runAddProductWindow();
|
||||
void runExportCsvWindow();
|
||||
void example_window();
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"name":"mozzarrella",
|
||||
"sold":1
|
||||
},
|
||||
{
|
||||
"name":"3x2 mozzarelle",
|
||||
"sold":1
|
||||
},
|
||||
{
|
||||
"name":"Scuola",
|
||||
"sold":2
|
||||
}
|
||||
]
|
||||
+221
-22
@@ -5,6 +5,8 @@
|
||||
#include <json-c/json.h>
|
||||
|
||||
#include "../include/logic.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
+73
-18
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user