Compare commits
9
Commits
0.1.0
...
6fb76ebed6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fb76ebed6 | ||
|
|
c670aceafb | ||
|
|
fa0e377811 | ||
|
|
defa951e51 | ||
|
|
c088caf03e | ||
|
|
8cd79da5eb | ||
|
|
8b5b690435 | ||
|
|
c6543cc583 | ||
|
|
05237cc7f1 |
No files matched your search
@@ -3,9 +3,10 @@ Un gestore di inventario e vendite minimale
|
|||||||
|
|
||||||
## Scaricarlo
|
## Scaricarlo
|
||||||
Disponibile solo per linux.
|
Disponibile solo per linux.
|
||||||
Scarica l'ultima versione da [qui](https://git.lug.uniroma2.it/rexeref/productManager/releases)!
|
Scarica l'ultima versione dalla pagina delle releases!
|
||||||
Avvia l'eseguibile da un terminale.
|
Avvia l'eseguibile da un terminale.
|
||||||
Se non si avvia rendilo prima un eseguibile con chmod +x
|
|
||||||
|
|
||||||
|
|
||||||
***questo codice molto probabilmente NON è sicuro, utilizzarlo a propria indiscrezione***
|
***questo codice molto probabilmente NON è sicuro, utilizzarlo a propria indiscrezione***
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
wip
|
||||||
@@ -7,5 +7,6 @@
|
|||||||
|
|
||||||
extern char productPricesFileLocation[64]; // Dichiarazione extern
|
extern char productPricesFileLocation[64]; // Dichiarazione extern
|
||||||
extern char countFileLocation[64];
|
extern char countFileLocation[64];
|
||||||
|
extern char configFileLocation[64];
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef JSONFILE_H
|
||||||
|
#define JSONFILE_H
|
||||||
|
|
||||||
|
#include <json-c/json.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verifica se un file esiste sul disco.
|
||||||
|
* @param fname Percorso del file da controllare.
|
||||||
|
* @return 1 se il file esiste ed è accessibile, 0 altrimenti.
|
||||||
|
*/
|
||||||
|
int existsFile(const char *fname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crea un nuovo file JSON inizializzandolo con un array vuoto ([]).
|
||||||
|
* @param fname Percorso del file da creare.
|
||||||
|
* @return 0 in caso di successo, -1 in caso di errore.
|
||||||
|
*/
|
||||||
|
int createFile(const char *fname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rinomina o sposta un file esistente.
|
||||||
|
* @param oldName Vecchio percorso/nome del file.
|
||||||
|
* @param newName Nuovo percorso/nome del file.
|
||||||
|
* @return 0 in caso di successo, -1 in caso di errore.
|
||||||
|
*/
|
||||||
|
int renameDataFile(const char *oldName, const char *newName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Carica un array JSON da file. Se il file non esiste, lo crea automaticamente.
|
||||||
|
* @param fname Percorso del file JSON.
|
||||||
|
* @return Puntatore all'oggetto JSON (array) caricato, oppure NULL in caso di errore.
|
||||||
|
*/
|
||||||
|
json_object *loadJsonArray(const char *fname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Salva un array JSON su file formattandolo in modo leggibile (indentato).
|
||||||
|
* @param fname Percorso del file di destinazione.
|
||||||
|
* @param arr L'oggetto JSON (array) da salvare.
|
||||||
|
* @return 0 in caso di successo, -1 in caso di errore.
|
||||||
|
*/
|
||||||
|
int saveJsonArray(const char *fname, json_object *arr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cerca un oggetto all'interno di un array JSON basandosi sul campo "name".
|
||||||
|
* @param arr L'array JSON in cui effettuare la ricerca.
|
||||||
|
* @param name La stringa del nome da cercare.
|
||||||
|
* @return Puntatore all'oggetto JSON trovato, oppure NULL se non esiste.
|
||||||
|
*/
|
||||||
|
json_object *findItemByName(json_object *arr, const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rimuove un oggetto da un array JSON basandosi sul campo "name".
|
||||||
|
* @param arr L'array JSON da cui rimuovere l'elemento.
|
||||||
|
* @param name Il nome dell'elemento da eliminare.
|
||||||
|
* @return 0 in caso di successo, 1 se l'elemento non viene trovato, -1 in caso di errore.
|
||||||
|
*/
|
||||||
|
int removeItemByName(json_object *arr, const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modifica il campo "name" di un elemento all'interno di un array JSON.
|
||||||
|
* @param arr L'array JSON contenente l'elemento.
|
||||||
|
* @param oldName Il nome attuale dell'elemento da rinominare.
|
||||||
|
* @param newName Il nuovo nome da assegnare.
|
||||||
|
* @return 0 in caso di successo, 1 se l'elemento non viene trovato, -1 in caso di errore.
|
||||||
|
*/
|
||||||
|
int renameItemByName(json_object *arr, const char *oldName, const char *newName);
|
||||||
|
|
||||||
|
#endif
|
||||||
+152
-7
@@ -1,12 +1,13 @@
|
|||||||
#ifndef LOGIC_H
|
#ifndef LOGIC_H
|
||||||
#define LOGIC_H
|
#define LOGIC_H
|
||||||
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
#include <json-c/json.h>
|
#include "jsonFile.h"
|
||||||
|
|
||||||
|
#define PRODUCT_STOCK_UNLIMITED (-1)
|
||||||
|
|
||||||
struct product {
|
struct product {
|
||||||
float cost;
|
float cost;
|
||||||
@@ -14,23 +15,167 @@ struct product {
|
|||||||
char name;
|
char name;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* File */
|
/*
|
||||||
int existsFile(const char *fname);
|
* Modello dati: nel file prodotti si salva solo la giacenza INIZIALE ("initial").
|
||||||
int createFile(const char *fname);
|
* Lo stock corrente e' sempre calcolato: initial - copie vendute
|
||||||
int renameDataFile(const char *oldName, const char *newName);
|
* (comprese quelle consumate tramite bundle). Le vendite scrivono solo il file vendite.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
/* Prodotti */
|
/* Prodotti */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restituisce l'array JSON contenente tutti i prodotti, cosi' come salvati su file
|
||||||
|
* (quindi con "initial" e senza stock corrente).
|
||||||
|
* @param fname Percorso del file JSON dei prodotti.
|
||||||
|
* @return Array JSON (da liberare con json_object_put()), oppure NULL in caso di errore.
|
||||||
|
*/
|
||||||
json_object *getProducts(const char *fname);
|
json_object *getProducts(const char *fname);
|
||||||
void listProducts(const char *fname);
|
|
||||||
|
/**
|
||||||
|
* @brief Come getProducts, ma ogni prodotto ha in piu' il campo "stock" calcolato
|
||||||
|
* (-1 = infinito; i bundle hanno sempre -1). E' una vista in memoria: non va salvata.
|
||||||
|
* @param productsFile Percorso del file JSON dei prodotti.
|
||||||
|
* @param soldFile Percorso del file JSON delle vendite (se manca, si assume nessuna vendita).
|
||||||
|
* @return Array JSON (da liberare con json_object_put()), oppure NULL in caso di errore.
|
||||||
|
*/
|
||||||
|
json_object *getProductsWithStock(const char *productsFile, const char *soldFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aggiunge un nuovo prodotto con prezzo e inventario illimitato.
|
||||||
|
* @return 0 in caso di successo, 1 se il prodotto esiste gia', -1 in caso di errore.
|
||||||
|
*/
|
||||||
int addProduct(const char *fname, const char *name, double price);
|
int addProduct(const char *fname, const char *name, double price);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aggiunge un nuovo prodotto specificando prezzo e giacenza iniziale.
|
||||||
|
* @param stock Giacenza iniziale (negativo per inventario illimitato).
|
||||||
|
* @return 0 in caso di successo, 1 se il prodotto esiste gia', -1 in caso di errore.
|
||||||
|
*/
|
||||||
|
int addProductWithInventory(const char *fname, const char *name, double price, int stock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aggiunge un prodotto composto (bundle).
|
||||||
|
* @param components Array JSON di {"name": ..., "qty": ...}; il bundle ne prende possesso in caso di successo.
|
||||||
|
* @return 0 in caso di successo, 1 se il prodotto esiste gia', -1 in caso di errore.
|
||||||
|
*/
|
||||||
|
int addBundleProduct(const char *fname, const char *name, double price, json_object *components);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ottiene il prezzo di un prodotto.
|
||||||
|
* @return Il prezzo, oppure -1.0 se non trovato o in caso di errore.
|
||||||
|
*/
|
||||||
double getPrice(const char *fname, const char *name);
|
double getPrice(const char *fname, const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aggiorna nome e prezzo di un prodotto lasciando invariato lo stock corrente.
|
||||||
|
* @return 0 ok, 1 non trovato, 2 nome gia' usato, -1 errore.
|
||||||
|
*/
|
||||||
|
int updateProduct(const char *productsFile, const char *soldFile, const char *oldName, const char *newName, double price);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aggiorna nome, prezzo e stock di un prodotto.
|
||||||
|
* Se il nome cambia aggiorna nella stessa operazione file vendite e riferimenti nei bundle.
|
||||||
|
* @param stock Giacenza ATTUALE desiderata (negativo = infinita).
|
||||||
|
* @return 0 ok, 1 non trovato, 2 nome gia' usato, -1 errore.
|
||||||
|
*/
|
||||||
|
int updateProductWithInventory(const char *productsFile, const char *soldFile, const char *oldName, const char *newName, double price, int stock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Legge lo stock corrente (calcolato) di un prodotto.
|
||||||
|
* @param productsFile Percorso del file JSON dei prodotti.
|
||||||
|
* @param soldFile Percorso del file JSON delle vendite.
|
||||||
|
* @param name Nome del prodotto.
|
||||||
|
* @param stock Quantita' corrente (PRODUCT_STOCK_UNLIMITED se non tracciato).
|
||||||
|
* @param tracked 1 se l'inventario e' tracciato, 0 altrimenti.
|
||||||
|
* @return 0 ok, 1 prodotto non trovato, -1 errore.
|
||||||
|
*/
|
||||||
|
int getProductInventory(const char *productsFile, const char *soldFile, const char *name, int *stock, int *tracked);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
/* Copie vendute */
|
/* Copie vendute */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Numero di copie vendute di un prodotto.
|
||||||
|
* @return Copie vendute (0 se non presente, -1 in caso di errore).
|
||||||
|
*/
|
||||||
int getCount(const char *fname, const char *name);
|
int getCount(const char *fname, const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stampa a schermo (ncurses) le vendite registrate per ciascun prodotto.
|
||||||
|
*/
|
||||||
void listSold(const char *fname);
|
void listSold(const char *fname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aggiunge copie vendute per un prodotto (crea la voce se non esiste).
|
||||||
|
* Non controlla lo stock: per questo usare recordSale().
|
||||||
|
* @return 0 ok, -1 errore.
|
||||||
|
*/
|
||||||
int addSold(const char *fname, const char *name, int qty);
|
int addSold(const char *fname, const char *name, int qty);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Elimina la registrazione delle vendite di un prodotto.
|
||||||
|
* Lo stock si "ripristina" da solo perche' e' derivato.
|
||||||
|
* @param productsFile Non usato, mantenuto per compatibilita'.
|
||||||
|
* @return 0 ok, 1 vendita non presente, -1 errore.
|
||||||
|
*/
|
||||||
|
int deleteSold(const char *productsFile, const char *soldFile, const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Imposta le copie vendute di un prodotto; se aumentano controlla la disponibilita'.
|
||||||
|
* @return 0 ok, 1 vendita non presente, 2 scorta insufficiente, -1 errore.
|
||||||
|
*/
|
||||||
|
int updateSold(const char *productsFile, const char *soldFile, const char *name, int qty);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Registra una vendita dopo aver verificato la disponibilita' (anche dei componenti, se bundle).
|
||||||
|
* Scrive solo il file vendite.
|
||||||
|
* @return 0 ok, 1 prodotto inesistente, 2 scorta insufficiente, -1 errore.
|
||||||
|
*/
|
||||||
|
int recordSale(const char *productsFile, const char *soldFile, const char *name, int qty);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
/* Entrate */
|
/* Entrate */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Entrate totali: somma di prezzo * copie vendute.
|
||||||
|
* @return Il totale, oppure -1.0 in caso di errore.
|
||||||
|
*/
|
||||||
double getTotalRevenue(const char *productsFile, const char *soldFile);
|
double getTotalRevenue(const char *productsFile, const char *soldFile);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Export */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Esporta prodotti e vendite in CSV (separatore ';').
|
||||||
|
* @return 0 ok, -1 errore di lettura prodotti, -2 errore di apertura del file di destinazione.
|
||||||
|
*/
|
||||||
|
int exportSalesToCsv(const char *productsFile, const char *soldFile, const char *outFile);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Config */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crea il file di configurazione se non esiste.
|
||||||
|
* @return 0 ok, altro in caso di errore.
|
||||||
|
*/
|
||||||
|
int ensureAppConfig(const char *fname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Carica la configurazione dal file specificato.
|
||||||
|
* @return 0 ok, -1 errore.
|
||||||
|
*/
|
||||||
|
int loadAppConfig(const char *fname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Salva la configurazione corrente sul file specificato.
|
||||||
|
* @return 0 ok, -1 errore.
|
||||||
|
*/
|
||||||
|
int saveAppConfig(const char *fname);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -6,18 +6,95 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crea e restituisce una nuova finestra ncurses con i bordi tracciati.
|
||||||
|
* @param height Altezza della finestra.
|
||||||
|
* @param width Larghezza della finestra.
|
||||||
|
* @param starty Coordinata Y iniziale sullo schermo.
|
||||||
|
* @param startx Coordinata X iniziale sullo schermo.
|
||||||
|
* @return Puntatore all'oggetto WINDOW creato.
|
||||||
|
*/
|
||||||
WINDOW *create_newwin(int height, int width, int starty, int startx);
|
WINDOW *create_newwin(int height, int width, int starty, int startx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Distrugge una finestra ncurses creata in precedenza e libera la memoria associata.
|
||||||
|
* @param local_win Puntatore alla finestra da eliminare.
|
||||||
|
*/
|
||||||
void destroy_win(WINDOW *local_win);
|
void destroy_win(WINDOW *local_win);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stampa del testo all'interno di una finestra gestendo l'andata a capo automatica (word wrapping) per parola.
|
||||||
|
* @param win Finestra in cui stampare.
|
||||||
|
* @param start_y Coordinata Y iniziale all'interno della finestra.
|
||||||
|
* @param start_x Coordinata X iniziale all'interno della finestra.
|
||||||
|
* @param str Stringa di testo da stampare.
|
||||||
|
*/
|
||||||
void wwrap_print(WINDOW *win, int start_y, int start_x, const char *str);
|
void wwrap_print(WINDOW *win, int start_y, int start_x, const char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gestisce un interruttore grafico interattivo (stato ON/OFF) modificabile da tastiera.
|
||||||
|
* @param win Finestra di visualizzazione.
|
||||||
|
* @param y Coordinata Y dell'interruttore.
|
||||||
|
* @param x Coordinata X dell'interruttore.
|
||||||
|
* @param state Stato iniziale (1 per ON, 0 per OFF).
|
||||||
|
* @return Lo stato finale dell'interruttore dopo l'interazione dell'utente.
|
||||||
|
*/
|
||||||
int uiToggleSwitch(WINDOW *win, int y, int x, int state);
|
int uiToggleSwitch(WINDOW *win, int y, int x, int state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gestisce un campo di input di testo interattivo all'interno di una finestra ncurses.
|
||||||
|
* @param win Finestra in cui inserire il testo.
|
||||||
|
* @param y Coordinata Y.
|
||||||
|
* @param x Coordinata X.
|
||||||
|
* @param input Buffer contenente l'eventuale testo iniziale e in cui memorizzare il risultato digitato.
|
||||||
|
* @param max_len Lunghezza massima consentita per l'input.
|
||||||
|
*/
|
||||||
void uiTextInput(WINDOW *win, int y, int x, char *input, int max_len);
|
void uiTextInput(WINDOW *win, int y, int x, char *input, int max_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mostra un menu di scelta multipla navigabile da tastiera tramite frecce o tasti vim (j/k).
|
||||||
|
* @param win Finestra in cui mostrare il menu.
|
||||||
|
* @param start_y Coordinata Y iniziale.
|
||||||
|
* @param start_x Coordinata X iniziale.
|
||||||
|
* @param options Array di stringhe contenente le opzioni del menu.
|
||||||
|
* @param n_options Numero totale di opzioni disponibili.
|
||||||
|
* @return L'indice (1-based) della voce selezionata dall'utente.
|
||||||
|
*/
|
||||||
int uiSelectMenu(WINDOW *win, int start_y, int start_x, char **options, int n_options);
|
int uiSelectMenu(WINDOW *win, int start_y, int start_x, char **options, int n_options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stampa un menu all'interno di una finestra evidenziando una voce specifica.
|
||||||
|
* @param menu_win Finestra del menu.
|
||||||
|
* @param y Coordinata Y.
|
||||||
|
* @param x Coordinata X.
|
||||||
|
* @param highlight Indice della voce da evidenziare graficamente.
|
||||||
|
*/
|
||||||
void print_menu(WINDOW *menu_win, int y, int x, int highlight);
|
void print_menu(WINDOW *menu_win, int y, int x, int highlight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mostra una finestra di avviso (warning) utilizzando una stringa formattata (stile printf).
|
||||||
|
* @param fmt Stringa di formattazione.
|
||||||
|
* @param ... Argomenti variabili da formattare.
|
||||||
|
*/
|
||||||
void warningf(const char *fmt, ...);
|
void warningf(const char *fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Apre una finestra di popup con un messaggio di avviso e attende la pressione di un tasto per chiudersi.
|
||||||
|
* @param string Messaggio di errore o avviso da mostrare.
|
||||||
|
*/
|
||||||
void warning(char *string);
|
void warning(char *string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aggiorna la barra di stato in basso sullo schermo con un messaggio formattato (stile printf).
|
||||||
|
* @param fmt Stringa di formattazione.
|
||||||
|
* @param ... Argomenti variabili da formattare.
|
||||||
|
*/
|
||||||
void statusf(const char *fmt, ...);
|
void statusf(const char *fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scrive un messaggio direttamente nella barra di stato in basso sullo schermo.
|
||||||
|
* @param msg Messaggio da visualizzare nella status bar.
|
||||||
|
*/
|
||||||
void status(char *msg);
|
void status(char *msg);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -9,10 +9,12 @@
|
|||||||
int runMainMenu();
|
int runMainMenu();
|
||||||
void runOptionsMenu();
|
void runOptionsMenu();
|
||||||
void runSellWindow();
|
void runSellWindow();
|
||||||
|
void runCreateBundleWindow();
|
||||||
void runAboutWindow();
|
void runAboutWindow();
|
||||||
void runViewSalesWindow();
|
void runViewSalesWindow();
|
||||||
void runViewProductsWindow();
|
void runViewProductsWindow();
|
||||||
void runAddProductWindow();
|
void runAddProductWindow();
|
||||||
|
void runExportCsvWindow();
|
||||||
void example_window();
|
void example_window();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -36,6 +36,6 @@ bundle: $(OBJECTS)
|
|||||||
@rm -fr dist/
|
@rm -fr dist/
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILD_DIR) $(EXEC) dist *.json
|
rm -rf $(BUILD_DIR) $(EXEC) dist *.json .fpm
|
||||||
|
|
||||||
.PHONY: all clean bundle
|
.PHONY: all clean bundle
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
char productPricesFileLocation[64]="./pm-item-price-default.json";
|
char productPricesFileLocation[64]="./pm-item-price-default.json";
|
||||||
char countFileLocation[64]="./pm-item-sold-count-default.json";
|
char countFileLocation[64]="./pm-item-sold-count-default.json";
|
||||||
|
char configFileLocation[64]=".fpm";
|
||||||
|
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <json-c/json.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
+639
-127
@@ -6,148 +6,351 @@
|
|||||||
|
|
||||||
#include "../include/logic.h"
|
#include "../include/logic.h"
|
||||||
#include "../include/globals.h"
|
#include "../include/globals.h"
|
||||||
|
#include "../include/jsonFile.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Formato dei file (JSON):
|
* Formato dei file (JSON):
|
||||||
*
|
*
|
||||||
* File prodotti: [ {"name": "Maglietta S", "price": 19.90}, ... ]
|
* File prodotti: [ {"name": "Maglietta S", "price": 19.90, "initial": 12}, ... ]
|
||||||
|
* initial = giacenza INIZIALE (intero), oppure -1 = infinito
|
||||||
|
* bundle = 1, components = [ {"name": "...", "qty": 2}, ... ]
|
||||||
* File vendite: [ {"name": "Maglietta S", "sold": 12}, ... ]
|
* File vendite: [ {"name": "Maglietta S", "sold": 12}, ... ]
|
||||||
*
|
*
|
||||||
|
* Lo stock corrente NON e' salvato: e' calcolato al volo come
|
||||||
|
* initial - (vendite dirette + vendite di bundle che lo contengono)
|
||||||
|
* Quindi vendere / modificare / eliminare una vendita tocca solo il file vendite.
|
||||||
|
*
|
||||||
* Compilazione: aggiungere -ljson-c (e -lncurses) al linker.
|
* Compilazione: aggiungere -ljson-c (e -lncurses) al linker.
|
||||||
*
|
*
|
||||||
* TODO: Aggiungere la possibilità di registrare gli acquisti non compiuti
|
* TODO: Aggiungere la possibilita' di registrare gli acquisti non compiuti
|
||||||
* con intenzioni di acquisto.
|
* con intenzioni di acquisto.
|
||||||
* e.g. persona vuole acquistare maglietta S ma non c'è la taglia
|
* e.g. persona vuole acquistare maglietta S ma non c'e' la taglia
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define MAX_BUNDLE_DEPTH 8
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Utilità sui file */
|
/* Helper JSON */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
int existsFile(const char *fname) {
|
static int isBundleItem(json_object *item) {
|
||||||
FILE *file = fopen(fname, "r");
|
json_object *v = NULL;
|
||||||
if (file) {
|
return item && json_object_object_get_ex(item, "bundle", &v) && json_object_get_boolean(v);
|
||||||
fclose(file);
|
}
|
||||||
|
|
||||||
|
static const char *itemName(json_object *item) {
|
||||||
|
json_object *v = NULL;
|
||||||
|
if (item && json_object_object_get_ex(item, "name", &v) && v)
|
||||||
|
return json_object_get_string(v);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getComponentQty(json_object *component) {
|
||||||
|
json_object *v = NULL;
|
||||||
|
if (!component || !json_object_object_get_ex(component, "qty", &v) || !v)
|
||||||
return 1;
|
return 1;
|
||||||
|
return json_object_get_int(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_object *getBundleComponents(json_object *bundleItem) {
|
||||||
|
json_object *components = NULL;
|
||||||
|
if (!bundleItem || !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 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);
|
||||||
|
const char *iname = itemName(item);
|
||||||
|
if (item != exclude && iname && strcmp(iname, name) == 0)
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* File vendite mancante/illeggibile = nessuna vendita. */
|
||||||
|
static json_object *loadSalesOrEmpty(const char *fname) {
|
||||||
|
json_object *arr = loadJsonArray(fname);
|
||||||
|
return arr ? arr : json_object_new_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Calcolo dello stock (sola lettura, nessun effetto collaterale) */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static int soldOf(json_object *sales, const char *name) {
|
||||||
|
json_object *item = findItemByName(sales, name);
|
||||||
|
json_object *v = NULL;
|
||||||
|
if (item && json_object_object_get_ex(item, "sold", &v) && v)
|
||||||
|
return json_object_get_int(v);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Giacenza iniziale salvata, o PRODUCT_STOCK_UNLIMITED se infinita/bundle. */
|
||||||
|
static int getInitial(json_object *item) {
|
||||||
|
json_object *v = NULL;
|
||||||
|
if (!item || isBundleItem(item) || !json_object_object_get_ex(item, "initial", &v) || !v)
|
||||||
|
return PRODUCT_STOCK_UNLIMITED;
|
||||||
|
int n = json_object_get_int(v);
|
||||||
|
return (n < 0) ? PRODUCT_STOCK_UNLIMITED : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Quante unita' di "target" consuma UNA vendita di "item".
|
||||||
|
* - prodotto semplice: 1 se e' il target, altrimenti 0
|
||||||
|
* - bundle: somma ricorsiva sui componenti (qty * consumo del componente)
|
||||||
|
*/
|
||||||
|
static long unitsNeeded(json_object *products, json_object *item, const char *target, int depth) {
|
||||||
|
if (!item || depth > MAX_BUNDLE_DEPTH)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!isBundleItem(item)) {
|
||||||
|
const char *n = itemName(item);
|
||||||
|
return (n && strcmp(n, target) == 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object *comps = getBundleComponents(item);
|
||||||
|
if (!comps)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
long total = 0;
|
||||||
|
size_t count = json_object_array_length(comps);
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
json_object *c = json_object_array_get_idx(comps, i);
|
||||||
|
const char *cname = itemName(c);
|
||||||
|
if (!cname)
|
||||||
|
continue;
|
||||||
|
total += (long)getComponentQty(c) *
|
||||||
|
unitsNeeded(products, findItemByName(products, cname), target, depth + 1);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unita' totali di "target" consumate da tutte le vendite registrate. */
|
||||||
|
static long consumedUnits(json_object *products, json_object *sales, const char *target) {
|
||||||
|
long total = 0;
|
||||||
|
size_t n = json_object_array_length(sales);
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
json_object *sale = json_object_array_get_idx(sales, i);
|
||||||
|
const char *sname = itemName(sale);
|
||||||
|
if (!sname)
|
||||||
|
continue;
|
||||||
|
json_object *prod = findItemByName(products, sname);
|
||||||
|
if (!prod)
|
||||||
|
continue; /* vendita orfana: prodotto non piu' in listino */
|
||||||
|
total += (long)soldOf(sales, sname) * unitsNeeded(products, prod, target, 0);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stock corrente di un prodotto semplice, o -1 se infinito. */
|
||||||
|
static long currentStock(json_object *products, json_object *sales, json_object *item) {
|
||||||
|
int initial = getInitial(item);
|
||||||
|
if (initial < 0)
|
||||||
|
return -1;
|
||||||
|
long left = initial - consumedUnits(products, sales, itemName(item));
|
||||||
|
return left < 0 ? 0 : left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Quante vendite di "item" sono ancora possibili.
|
||||||
|
* -1 = illimitate. Per un bundle e' il minimo tra le disponibilita' dei componenti.
|
||||||
|
*/
|
||||||
|
static long availableUnits(json_object *products, json_object *sales, json_object *item, int depth) {
|
||||||
|
if (!item || depth > MAX_BUNDLE_DEPTH)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!isBundleItem(item))
|
||||||
|
return currentStock(products, sales, item);
|
||||||
|
|
||||||
|
json_object *comps = getBundleComponents(item);
|
||||||
|
if (!comps)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
long best = -1;
|
||||||
|
size_t count = json_object_array_length(comps);
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
json_object *c = json_object_array_get_idx(comps, i);
|
||||||
|
const char *cname = itemName(c);
|
||||||
|
int q = getComponentQty(c);
|
||||||
|
if (!cname || q <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
json_object *child = findItemByName(products, cname);
|
||||||
|
if (!child)
|
||||||
|
return 0; /* componente sparito: bundle non vendibile */
|
||||||
|
|
||||||
|
long a = availableUnits(products, sales, child, depth + 1);
|
||||||
|
if (a < 0)
|
||||||
|
continue; /* componente illimitato */
|
||||||
|
long k = a / q;
|
||||||
|
if (best < 0 || k < best)
|
||||||
|
best = k;
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Scrittura atomica di due file (tmp + rename) */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static int writeTmp(const char *fname, json_object *arr, char *tmp, size_t size) {
|
||||||
|
snprintf(tmp, size, "%s.tmp", fname);
|
||||||
|
if (json_object_to_file_ext(tmp, arr, JSON_C_TO_STRING_PRETTY) != 0) {
|
||||||
|
remove(tmp);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Crea un file JSON contenente un array vuoto. Ritorna 0 se ok, -1 se errore. */
|
/* Scrive prima tutto su file temporanei, poi li rinomina. sales puo' essere NULL. */
|
||||||
int createFile(const char *fname) {
|
static int commitFiles(const char *productsFile, json_object *products,
|
||||||
json_object *arr = json_object_new_array();
|
const char *soldFile, json_object *sales) {
|
||||||
if (!arr)
|
char tmpP[512], tmpS[512];
|
||||||
|
|
||||||
|
if (writeTmp(productsFile, products, tmpP, sizeof tmpP) != 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
int res = json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY);
|
if (sales) {
|
||||||
json_object_put(arr);
|
if (writeTmp(soldFile, sales, tmpS, sizeof tmpS) != 0) {
|
||||||
return (res == 0) ? 0 : -1;
|
remove(tmpP);
|
||||||
}
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Cambia il nome di un file di dati. Ritorna 0 se ok, -1 se errore. */
|
if (rename(tmpP, productsFile) != 0) {
|
||||||
int renameDataFile(const char *oldName, const char *newName) {
|
remove(tmpP);
|
||||||
return (rename(oldName, newName) == 0) ? 0 : -1;
|
if (sales)
|
||||||
|
remove(tmpS);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (sales && rename(tmpS, soldFile) != 0) {
|
||||||
|
remove(tmpS);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Funzioni interne */
|
/* Configurazione */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
/*
|
static json_object *buildAppConfigObject(void) {
|
||||||
* Carica l'array JSON dal file (creandolo se non esiste).
|
json_object *obj = json_object_new_object();
|
||||||
* Il chiamante deve liberare il risultato con json_object_put().
|
if (!obj)
|
||||||
* 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;
|
return NULL;
|
||||||
|
|
||||||
/* Un file vuoto (e.g. creato a mano o dalla vecchia createFile)
|
json_object_object_add(obj, "productsFile", json_object_new_string(productPricesFileLocation));
|
||||||
* non e' JSON valido: lo trattiamo come un array vuoto. */
|
json_object_object_add(obj, "salesFile", json_object_new_string(countFileLocation));
|
||||||
FILE *f = fopen(fname, "r");
|
return obj;
|
||||||
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 int writeAppConfigObject(const char *fname, json_object *obj) {
|
||||||
static json_object *findByName(json_object *arr, const char *name) {
|
if (!obj)
|
||||||
size_t n = json_object_array_length(arr);
|
return -1;
|
||||||
|
return (json_object_to_file_ext(fname, obj, JSON_C_TO_STRING_PRETTY) == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < n; i++) {
|
static int readConfigString(json_object *obj, const char *key, char *out, size_t outSize) {
|
||||||
json_object *item = json_object_array_get_idx(arr, i);
|
json_object *v = NULL;
|
||||||
json_object *jname;
|
const char *s = NULL;
|
||||||
|
|
||||||
if (json_object_object_get_ex(item, "name", &jname) &&
|
if (!obj || !key || !out || outSize == 0)
|
||||||
strcmp(json_object_get_string(jname), name) == 0)
|
return 0;
|
||||||
return item;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (ensureAppConfig(fname) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
json_object *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);
|
||||||
}
|
}
|
||||||
return NULL;
|
|
||||||
|
readConfigString(loaded, "productsFile", productPricesFileLocation, sizeof productPricesFileLocation);
|
||||||
|
readConfigString(loaded, "salesFile", countFileLocation, sizeof countFileLocation);
|
||||||
|
json_object_put(loaded);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Prodotti */
|
/* Prodotti */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
/*
|
/* Array JSON dei prodotti (da liberare con json_object_put()), NULL se errore. */
|
||||||
* Ritorna l'array JSON dei prodotti (da liberare con json_object_put()),
|
|
||||||
* oppure NULL in caso di errore.
|
|
||||||
*/
|
|
||||||
json_object *getProducts(const char *fname) {
|
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);
|
|
||||||
if (!arr) {
|
|
||||||
printw("Errore nella lettura del file %s\n", fname);
|
|
||||||
refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t n = json_object_array_length(arr);
|
|
||||||
if (n == 0)
|
|
||||||
printw("Nessun prodotto presente.\n");
|
|
||||||
|
|
||||||
for (size_t i = 0; i < n; i++) {
|
|
||||||
json_object *item = json_object_array_get_idx(arr, i);
|
|
||||||
json_object *jname, *jprice;
|
|
||||||
|
|
||||||
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,
|
|
||||||
json_object_get_string(jname),
|
|
||||||
json_object_get_double(jprice));
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
json_object_put(arr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Aggiunge un prodotto con il suo prezzo.
|
* Come getProducts, ma ogni prodotto semplice ha in piu' un campo "stock"
|
||||||
* Ritorna 0 se ok, -1 se errore, 1 se il prodotto esiste già.
|
* CALCOLATO (-1 = infinito); i bundle hanno sempre -1.
|
||||||
|
* E' una vista in memoria: NON va mai salvata su file.
|
||||||
|
* Cosi' la UI che legge "stock" continua a funzionare senza modifiche.
|
||||||
*/
|
*/
|
||||||
|
json_object *getProductsWithStock(const char *productsFile, const char *soldFile) {
|
||||||
|
json_object *products = loadJsonArray(productsFile);
|
||||||
|
if (!products)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
json_object *sales = loadSalesOrEmpty(soldFile);
|
||||||
|
|
||||||
|
size_t n = json_object_array_length(products);
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
json_object *item = json_object_array_get_idx(products, i);
|
||||||
|
long st = isBundleItem(item) ? -1 : currentStock(products, sales, item);
|
||||||
|
json_object_object_add(item, "stock", json_object_new_int((int)st));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_put(sales);
|
||||||
|
return products;
|
||||||
|
}
|
||||||
|
|
||||||
int addProduct(const char *fname, const char *name, double price) {
|
int addProduct(const char *fname, const char *name, double price) {
|
||||||
json_object *arr = loadArray(fname);
|
return addProductWithInventory(fname, name, price, PRODUCT_STOCK_UNLIMITED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stock = giacenza di partenza (negativo = infinita). 0 ok, 1 esiste gia', -1 errore. */
|
||||||
|
int addProductWithInventory(const char *fname, const char *name, double price, int stock) {
|
||||||
|
json_object *arr = loadJsonArray(fname);
|
||||||
if (!arr)
|
if (!arr)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (findByName(arr, name)) {
|
if (findItemByName(arr, name)) {
|
||||||
json_object_put(arr);
|
json_object_put(arr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -155,21 +358,51 @@ int addProduct(const char *fname, const char *name, double price) {
|
|||||||
json_object *item = json_object_new_object();
|
json_object *item = json_object_new_object();
|
||||||
json_object_object_add(item, "name", json_object_new_string(name));
|
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, "price", json_object_new_double(price));
|
||||||
|
json_object_object_add(item, "initial",
|
||||||
|
json_object_new_int(stock < 0 ? PRODUCT_STOCK_UNLIMITED : stock));
|
||||||
json_object_array_add(arr, item);
|
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, "components", components);
|
||||||
|
json_object_array_add(arr, item);
|
||||||
|
|
||||||
|
int res = saveJsonArray(fname, arr);
|
||||||
json_object_put(arr);
|
json_object_put(arr);
|
||||||
return (res == 0) ? 0 : -1;
|
return (res == 0) ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ritorna il prezzo di un prodotto, oppure -1.0 se non trovato/errore. */
|
/* Ritorna il prezzo di un prodotto, oppure -1.0 se non trovato/errore. */
|
||||||
double getPrice(const char *fname, const char *name) {
|
double getPrice(const char *fname, const char *name) {
|
||||||
json_object *arr = loadArray(fname);
|
json_object *arr = loadJsonArray(fname);
|
||||||
if (!arr)
|
if (!arr)
|
||||||
return -1.0;
|
return -1.0;
|
||||||
|
|
||||||
double price = -1.0;
|
double price = -1.0;
|
||||||
json_object *item = findByName(arr, name);
|
json_object *item = findItemByName(arr, name);
|
||||||
json_object *jprice;
|
json_object *jprice;
|
||||||
|
|
||||||
if (item && json_object_object_get_ex(item, "price", &jprice))
|
if (item && json_object_object_get_ex(item, "price", &jprice))
|
||||||
@@ -179,30 +412,135 @@ double getPrice(const char *fname, const char *name) {
|
|||||||
return price;
|
return price;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stock corrente di un prodotto (calcolato).
|
||||||
|
* 0 ok, 1 prodotto non trovato, -1 errore.
|
||||||
|
*/
|
||||||
|
int getProductInventory(const char *productsFile, const char *soldFile, const char *name,
|
||||||
|
int *stock, int *tracked) {
|
||||||
|
if (!stock || !tracked)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
json_object *products = loadJsonArray(productsFile);
|
||||||
|
if (!products)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
json_object *item = findItemByName(products, name);
|
||||||
|
if (!item) {
|
||||||
|
json_object_put(products);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBundleItem(item)) {
|
||||||
|
*stock = PRODUCT_STOCK_UNLIMITED;
|
||||||
|
*tracked = 0;
|
||||||
|
} else {
|
||||||
|
json_object *sales = loadSalesOrEmpty(soldFile);
|
||||||
|
long st = currentStock(products, sales, item);
|
||||||
|
json_object_put(sales);
|
||||||
|
*tracked = (st >= 0);
|
||||||
|
*stock = (st >= 0) ? (int)st : PRODUCT_STOCK_UNLIMITED;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_put(products);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Modifica un prodotto. "stock" e' la giacenza ATTUALE desiderata
|
||||||
|
* (negativo = infinita): internamente diventa initial = stock + gia' consumato.
|
||||||
|
* Se il nome cambia, aggiorna nella stessa operazione file vendite e
|
||||||
|
* riferimenti nei bundle.
|
||||||
|
* 0 ok, 1 non trovato, 2 nome gia' usato, -1 errore.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int renamed = strcmp(oldName, newName) != 0;
|
||||||
|
if (renamed && findItemByNameExcluding(products, newName, item)) {
|
||||||
|
json_object_put(products);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object *sales = loadSalesOrEmpty(soldFile);
|
||||||
|
|
||||||
|
/* va calcolato PRIMA di rinominare */
|
||||||
|
if (!isBundleItem(item)) {
|
||||||
|
int initial = (stock < 0) ? PRODUCT_STOCK_UNLIMITED
|
||||||
|
: (int)(stock + consumedUnits(products, sales, oldName));
|
||||||
|
json_object_object_add(item, "initial", json_object_new_int(initial));
|
||||||
|
}
|
||||||
|
json_object_object_del(item, "stock"); /* residuo del vecchio formato */
|
||||||
|
json_object_object_add(item, "price", json_object_new_double(price));
|
||||||
|
|
||||||
|
if (renamed) {
|
||||||
|
json_object_object_add(item, "name", json_object_new_string(newName));
|
||||||
|
|
||||||
|
/* riferimenti nei bundle */
|
||||||
|
size_t n = json_object_array_length(products);
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
json_object *comps = getBundleComponents(json_object_array_get_idx(products, i));
|
||||||
|
if (!comps)
|
||||||
|
continue;
|
||||||
|
size_t m = json_object_array_length(comps);
|
||||||
|
for (size_t j = 0; j < m; j++) {
|
||||||
|
json_object *c = json_object_array_get_idx(comps, j);
|
||||||
|
const char *cn = itemName(c);
|
||||||
|
if (cn && strcmp(cn, oldName) == 0)
|
||||||
|
json_object_object_add(c, "name", json_object_new_string(newName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vendite (se non ce ne sono, niente da rinominare: non e' un errore) */
|
||||||
|
json_object *sitem = findItemByName(sales, oldName);
|
||||||
|
if (sitem)
|
||||||
|
json_object_object_add(sitem, "name", json_object_new_string(newName));
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = commitFiles(productsFile, products, renamed ? soldFile : NULL, renamed ? sales : NULL);
|
||||||
|
json_object_put(products);
|
||||||
|
json_object_put(sales);
|
||||||
|
return (res == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int updateProduct(const char *productsFile, const char *soldFile,
|
||||||
|
const char *oldName, const char *newName, double price) {
|
||||||
|
int stock = PRODUCT_STOCK_UNLIMITED, tracked = 0;
|
||||||
|
int r = getProductInventory(productsFile, soldFile, oldName, &stock, &tracked);
|
||||||
|
if (r != 0)
|
||||||
|
return r;
|
||||||
|
return updateProductWithInventory(productsFile, soldFile, oldName, newName, price,
|
||||||
|
tracked ? stock : PRODUCT_STOCK_UNLIMITED);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Copie vendute */
|
/* Copie vendute */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
/* Ritorna le copie vendute di un prodotto (0 se non presente, -1 se errore). */
|
/* Ritorna le copie vendute di un prodotto (0 se non presente, -1 se errore). */
|
||||||
int getCount(const char *fname, const char *name) {
|
int getCount(const char *fname, const char *name) {
|
||||||
json_object *arr = loadArray(fname);
|
json_object *arr = loadJsonArray(fname);
|
||||||
if (!arr)
|
if (!arr)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
int count = 0;
|
int count = soldOf(arr, name);
|
||||||
json_object *item = findByName(arr, name);
|
|
||||||
json_object *jsold;
|
|
||||||
|
|
||||||
if (item && json_object_object_get_ex(item, "sold", &jsold))
|
|
||||||
count = json_object_get_int(jsold);
|
|
||||||
|
|
||||||
json_object_put(arr);
|
json_object_put(arr);
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stampa a schermo (ncurses) le copie vendute di ogni prodotto. */
|
/* Stampa a schermo (ncurses) le copie vendute di ogni prodotto. */
|
||||||
void listSold(const char *fname) {
|
void listSold(const char *fname) {
|
||||||
json_object *arr = loadArray(fname);
|
json_object *arr = loadJsonArray(fname);
|
||||||
if (!arr) {
|
if (!arr) {
|
||||||
printw("Errore nella lettura del file %s\n", fname);
|
printw("Errore nella lettura del file %s\n", fname);
|
||||||
refresh();
|
refresh();
|
||||||
@@ -228,26 +566,16 @@ void listSold(const char *fname) {
|
|||||||
json_object_put(arr);
|
json_object_put(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Aggiunge "qty" copie vendute (crea la voce se non esiste). 0 ok, -1 errore. */
|
||||||
* Aggiunge "qty" copie vendute al prodotto indicato
|
|
||||||
* (crea la voce se non esiste ancora).
|
|
||||||
* Ritorna 0 se ok, -1 se errore.
|
|
||||||
*/
|
|
||||||
int addSold(const char *fname, const char *name, int qty) {
|
int addSold(const char *fname, const char *name, int qty) {
|
||||||
json_object *arr = loadArray(fname);
|
json_object *arr = loadJsonArray(fname);
|
||||||
if (!arr)
|
if (!arr)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
json_object *item = findByName(arr, name);
|
json_object *item = findItemByName(arr, name);
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
json_object *jsold;
|
json_object_object_add(item, "sold", json_object_new_int(soldOf(arr, name) + qty));
|
||||||
int current = 0;
|
|
||||||
|
|
||||||
if (json_object_object_get_ex(item, "sold", &jsold))
|
|
||||||
current = json_object_get_int(jsold);
|
|
||||||
|
|
||||||
json_object_object_add(item, "sold", json_object_new_int(current + qty));
|
|
||||||
} else {
|
} else {
|
||||||
item = json_object_new_object();
|
item = json_object_new_object();
|
||||||
json_object_object_add(item, "name", json_object_new_string(name));
|
json_object_object_add(item, "name", json_object_new_string(name));
|
||||||
@@ -255,25 +583,124 @@ int addSold(const char *fname, const char *name, int qty) {
|
|||||||
json_object_array_add(arr, item);
|
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);
|
json_object_put(arr);
|
||||||
return (res == 0) ? 0 : -1;
|
return (res == 0) ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Registra una vendita. Scrive UN solo file (vendite).
|
||||||
|
* 0 ok, 1 prodotto inesistente, 2 scorta insufficiente, -1 errore.
|
||||||
|
*/
|
||||||
|
int recordSale(const char *productsFile, const char *soldFile, const char *name, int qty) {
|
||||||
|
if (qty <= 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!existsFile(productsFile) && createFile(productsFile) != 0)
|
||||||
|
return -1;
|
||||||
|
if (!existsFile(soldFile) && createFile(soldFile) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
json_object *products = loadJsonArray(productsFile);
|
||||||
|
if (!products)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
json_object *item = findItemByName(products, name);
|
||||||
|
if (!item) {
|
||||||
|
json_object_put(products);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object *sales = loadSalesOrEmpty(soldFile);
|
||||||
|
long avail = availableUnits(products, sales, item, 0);
|
||||||
|
json_object_put(sales);
|
||||||
|
json_object_put(products);
|
||||||
|
|
||||||
|
if (avail >= 0 && qty > avail)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
return addSold(soldFile, name, qty) == 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Elimina una vendita. Lo stock si "ripristina" da solo (e' derivato).
|
||||||
|
* productsFile resta nella firma per compatibilita' con la UI.
|
||||||
|
* 0 ok, 1 vendita non presente, -1 errore.
|
||||||
|
*/
|
||||||
|
int deleteSold(const char *productsFile, const char *soldFile, const char *name) {
|
||||||
|
(void)productsFile;
|
||||||
|
|
||||||
|
json_object *sales = loadJsonArray(soldFile);
|
||||||
|
if (!sales)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!findItemByName(sales, name)) {
|
||||||
|
json_object_put(sales);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = removeItemByName(sales, name);
|
||||||
|
if (res == 0)
|
||||||
|
res = saveJsonArray(soldFile, sales);
|
||||||
|
json_object_put(sales);
|
||||||
|
return (res == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Imposta le copie vendute a "qty". Se aumentano, controlla la disponibilita'.
|
||||||
|
* 0 ok, 1 vendita non presente, 2 scorta insufficiente, -1 errore.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delta = qty - soldOf(sales, name);
|
||||||
|
|
||||||
|
if (delta > 0) {
|
||||||
|
json_object *products = loadJsonArray(productsFile);
|
||||||
|
if (!products) {
|
||||||
|
json_object_put(sales);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
json_object *prod = findItemByName(products, name);
|
||||||
|
if (prod) { /* se il prodotto non esiste piu' non c'e' stock da controllare */
|
||||||
|
long avail = availableUnits(products, sales, prod, 0);
|
||||||
|
if (avail >= 0 && delta > avail) {
|
||||||
|
json_object_put(products);
|
||||||
|
json_object_put(sales);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json_object_put(products);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(item, "sold", json_object_new_int(qty));
|
||||||
|
int res = saveJsonArray(soldFile, sales);
|
||||||
|
json_object_put(sales);
|
||||||
|
return (res == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Entrate */
|
/* Entrate */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
/*
|
/* Somma di (prezzo * copie vendute). Ritorna -1.0 in caso di errore. */
|
||||||
* Calcola le entrate totali: somma di (prezzo * copie vendute)
|
|
||||||
* per ogni prodotto. Ritorna -1.0 in caso di errore.
|
|
||||||
*/
|
|
||||||
double getTotalRevenue(const char *productsFile, const char *soldFile) {
|
double getTotalRevenue(const char *productsFile, const char *soldFile) {
|
||||||
json_object *products = loadArray(productsFile);
|
json_object *products = loadJsonArray(productsFile);
|
||||||
if (!products)
|
if (!products)
|
||||||
return -1.0;
|
return -1.0;
|
||||||
|
|
||||||
json_object *sales = loadArray(soldFile);
|
json_object *sales = loadJsonArray(soldFile);
|
||||||
if (!sales) {
|
if (!sales) {
|
||||||
json_object_put(products);
|
json_object_put(products);
|
||||||
return -1.0;
|
return -1.0;
|
||||||
@@ -290,7 +717,7 @@ double getTotalRevenue(const char *productsFile, const char *soldFile) {
|
|||||||
!json_object_object_get_ex(sale, "sold", &jsold))
|
!json_object_object_get_ex(sale, "sold", &jsold))
|
||||||
continue;
|
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))
|
if (prod && json_object_object_get_ex(prod, "price", &jprice))
|
||||||
total += json_object_get_double(jprice) * json_object_get_int(jsold);
|
total += json_object_get_double(jprice) * json_object_get_int(jsold);
|
||||||
}
|
}
|
||||||
@@ -299,3 +726,88 @@ double getTotalRevenue(const char *productsFile, const char *soldFile) {
|
|||||||
json_object_put(sales);
|
json_object_put(sales);
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Export CSV */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void csvField(FILE *f, const char *s) {
|
||||||
|
if (!s)
|
||||||
|
s = "";
|
||||||
|
if (!strpbrk(s, ";\"\n")) {
|
||||||
|
fputs(s, f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fputc('"', f);
|
||||||
|
for (; *s; s++) {
|
||||||
|
if (*s == '"')
|
||||||
|
fputc('"', f);
|
||||||
|
fputc(*s, f);
|
||||||
|
}
|
||||||
|
fputc('"', f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 0 ok, -1 errore lettura prodotti, -2 errore apertura file di destinazione. */
|
||||||
|
int exportSalesToCsv(const char *productsFile, const char *soldFile, const char *outFile) {
|
||||||
|
json_object *products = getProducts(productsFile);
|
||||||
|
if (!products || !json_object_is_type(products, json_type_array)) {
|
||||||
|
if (products)
|
||||||
|
json_object_put(products);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object *sales = loadSalesOrEmpty(soldFile); /* letto UNA volta sola */
|
||||||
|
|
||||||
|
FILE *f = fopen(outFile, "w");
|
||||||
|
if (!f) {
|
||||||
|
json_object_put(products);
|
||||||
|
json_object_put(sales);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f, "Nome;Inventario iniziale;Vendite;Prezzo;Componenti\n");
|
||||||
|
|
||||||
|
size_t n = json_object_array_length(products);
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
json_object *item = json_object_array_get_idx(products, i);
|
||||||
|
json_object *jprice = NULL;
|
||||||
|
const char *name = itemName(item);
|
||||||
|
double price = (json_object_object_get_ex(item, "price", &jprice) && jprice)
|
||||||
|
? json_object_get_double(jprice) : 0.0;
|
||||||
|
int sold = name ? soldOf(sales, name) : 0;
|
||||||
|
|
||||||
|
char inv[32];
|
||||||
|
int initial = getInitial(item);
|
||||||
|
if (initial < 0)
|
||||||
|
snprintf(inv, sizeof inv, "non definito");
|
||||||
|
else
|
||||||
|
snprintf(inv, sizeof inv, "%d", initial);
|
||||||
|
|
||||||
|
/* componenti separati da " | " (non da ';', che e' il separatore del CSV) */
|
||||||
|
char comps[512];
|
||||||
|
comps[0] = '\0';
|
||||||
|
json_object *clist = getBundleComponents(item);
|
||||||
|
if (isBundleItem(item) && clist) {
|
||||||
|
size_t off = 0;
|
||||||
|
size_t m = json_object_array_length(clist);
|
||||||
|
for (size_t j = 0; j < m && off < sizeof comps; j++) {
|
||||||
|
json_object *c = json_object_array_get_idx(clist, j);
|
||||||
|
int w = snprintf(comps + off, sizeof comps - off, "%s%s x%d",
|
||||||
|
j ? " | " : "", itemName(c) ? itemName(c) : "", getComponentQty(c));
|
||||||
|
if (w < 0)
|
||||||
|
break;
|
||||||
|
off += (size_t)w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
csvField(f, name);
|
||||||
|
fprintf(f, ";%s;%d;%.2f;", inv, sold, price);
|
||||||
|
csvField(f, comps);
|
||||||
|
fputc('\n', f);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
json_object_put(products);
|
||||||
|
json_object_put(sales);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+5
-25
@@ -22,6 +22,7 @@
|
|||||||
#include "../include/windowsDeclaration.h"
|
#include "../include/windowsDeclaration.h"
|
||||||
#include "../include/windowsBack.h"
|
#include "../include/windowsBack.h"
|
||||||
#include "../include/logic.h"
|
#include "../include/logic.h"
|
||||||
|
#include "../include/globals.h"
|
||||||
|
|
||||||
void printLogoBG();
|
void printLogoBG();
|
||||||
|
|
||||||
@@ -43,36 +44,15 @@ int main(){
|
|||||||
noecho();
|
noecho();
|
||||||
curs_set(0);
|
curs_set(0);
|
||||||
raw();
|
raw();
|
||||||
|
if (loadAppConfig(configFileLocation) != 0)
|
||||||
|
warningf("Impossibile caricare la config '%s', uso i valori di default.", configFileLocation);
|
||||||
printLogoBG();
|
printLogoBG();
|
||||||
statusf(" ");
|
statusf(" ");
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
|
while (runMainMenu()) {
|
||||||
int running = 0;
|
|
||||||
while(running != 6){
|
|
||||||
printLogoBG();
|
printLogoBG();
|
||||||
refresh();
|
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();
|
endwin();
|
||||||
@@ -81,6 +61,6 @@ int main(){
|
|||||||
|
|
||||||
|
|
||||||
void printLogoBG(){
|
void printLogoBG(){
|
||||||
wwrap_print(stdscr, LINES / 2 - 3, COLS / 2 - 5," ######### \n ################### \n ######## ##### \n ####### ### \n ###### ### \n ###### ### ## \n ############ # ### ## \n ################ ### # \n ############ ##### # \n ############## \n ######### \n ## \n ####### \n ## ## \n ## ## ###### ## ## # ## ## \n ###### ##### ######## #### ## ## ## # ## \n ## ## ## ## ## ## ## ##### ## ## ## # ## ## \n ## ## #### ## ## ## #### ####### ##### ##### ##### ");
|
wwrap_print(stdscr, LINES / 2 - 3, COLS / 2 - 5, " ######### \n ################### \n ######## ##### \n ####### ### \n ###### ### \n ###### ### ## \n ############ # ### ## \n ################ ### # \n ############ ##### # \n ############## \n ######### \n ## \n ####### \n ###### # ## \n ## ## ###### ## ## # ##### \n ###### ##### ######## #### ## ## ## # ## \n ## ## ## ## ## ## ## ## ## ## ## ## # ## ## \n ## ## #### ## ## ## ##### ####### ##### ##### #####");
|
||||||
mvprintw(LINES -1, COLS / 2 - 25, "Product Manager by Vincenzo Nikodem Cesqui Podiani");
|
mvprintw(LINES -1, COLS / 2 - 25, "Product Manager by Vincenzo Nikodem Cesqui Podiani");
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
|
|
||||||
#include "../include/windowsBack.h"
|
#include "../include/windowsBack.h"
|
||||||
|
|||||||
+822
-117
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user