37 Commits
Author SHA1 Message Date
admin 6fb76ebed6 Patch a funzionamento registrazione vendite 2026-10-11 14:29:14 +02:00
admin c670aceafb Aggiustata scritta roma2lug nel logo 2026-10-11 14:00:14 +02:00
admin fa0e377811 Aggiunti commenti doxygen a include 2026-10-11 13:45:37 +02:00
rexeref defa951e51 pulito file tree 2026-10-10 20:41:45 +02:00
rexeref c088caf03e rimosso .fpm dal tree 2026-10-10 20:40:49 +02:00
rexeref 8cd79da5eb Aggiunta gestione export csv 2026-10-10 20:40:24 +02:00
rexeref 8b5b690435 pulizia da file superflui 2026-10-10 19:19:00 +02:00
rexeref c6543cc583 Modifiche e correzioni generali ai assisted 2026-10-10 19:18:28 +02:00
rexeref 05237cc7f1 Update README.md 2026-10-09 17:33:58 +02:00
admin 176c2d8be5 Aggiunta creazione pacchetto tar di distribuzione 2026-10-09 17:27:33 +02:00
admin 5db402c7cf aggiunta logica di gestione dati. Assistito con ai 2026-10-09 17:18:38 +02:00
admin 1a15d93f76 Merge branch 'main' of git.lug.uniroma2.it:rexeref/productManager 2026-05-29 22:15:26 +02:00
admin 0471375aab commentato 2026-05-29 22:14:52 +02:00
rexeref 2427831b9b Update README.md 2026-05-29 21:36:55 +02:00
admin 42f3b43a68 Aggiunto README.md 2026-05-29 21:36:01 +02:00
admin c69c420781 Code refactor 2026-05-29 21:10:06 +02:00
admin 673b58caec io ODIO vscode 2026-05-29 17:38:43 +02:00
admin 32365b9617 Pulito build/* 2026-05-29 17:25:12 +02:00
admin 90b4e7db00 Merge branch 'main' of git.lug.uniroma2.it:rexeref/productManager 2026-05-29 17:24:08 +02:00
admin e4f307f183 Correzioni varie in gestione compilazione 2026-05-29 17:01:02 +02:00
admin fac33176ed rimosso dal branch il file di samuele 2025-10-31 15:40:55 +01:00
admin e8d5e7d2b1 aggiunto file .gitignore 2025-10-31 14:37:16 +01:00
Moretti a0314628bd rimosso file inutile come me 2025-10-31 14:34:27 +01:00
admin b5d9009ce1 Test spiegazione francesco 2025-10-31 14:34:27 +01:00
admin 7eee20414c Controllato funzionamento existsFile() createFile() in logic.c 2025-10-31 14:34:27 +01:00
admin d82b5466b0 Rimossi file inutili 2025-10-31 14:34:27 +01:00
admin 7c7859dc90 Commit di recap 2025-10-31 14:34:27 +01:00
admin 2f3ef9736c 14-10-25 2025-10-31 14:34:27 +01:00
admin 07c7235831 Cambiata estensione file di lettura da txt a fpm 2025-10-31 14:34:27 +01:00
admin f18d0cf043 Preparato ambiente di testing allo sviluppo 2025-10-31 14:34:27 +01:00
admin 619774c809 Aggiunto ambiente di testing funzioni slegate a ncurses 2025-10-31 14:34:27 +01:00
admin 29c375b20c Aggiunto logo all'avvio 2025-10-31 14:34:27 +01:00
admin 750f7a9149 Aggiustati dettagli estetici e cambiate funzioni di print varie 2025-10-31 14:34:27 +01:00
admin 5508532619 Ristrutturato in più file 2025-10-31 14:34:27 +01:00
admin 499b3787c6 Pierosforo 2025-10-31 14:34:27 +01:00
admin 4d9eba1512 primo commit 2025-10-31 14:34:27 +01:00
morels 99d5e5244c Orribile prima versione scritta da Samuele 2025-06-12 17:24:13 +02:00
29 changed files with 2735 additions and 408 deletions

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
build/
.vscode/
productManager
test
-11
View File
@@ -1,11 +0,0 @@
{
"makefile.configureOnOpen": false,
"files.associations": {
"ncurses.h": "c",
"logic.h": "c",
"stdio.h": "c",
"globals.h": "c",
"windows.h": "c",
"stdlib.h": "c"
}
}
+12
View File
@@ -0,0 +1,12 @@
# Product Manager
Un gestore di inventario e vendite minimale
## Scaricarlo
Disponibile solo per linux.
Scarica l'ultima versione dalla pagina delle releases!
Avvia l'eseguibile da un terminale.
***questo codice molto probabilmente NON è sicuro, utilizzarlo a propria indiscrezione***
## Troubleshooting
wip
-2
View File
@@ -1,2 +0,0 @@
build/globals.o: src/globals.c src/../include/globals.h
src/../include/globals.h:
BIN
View File
Binary file not shown.
-4
View File
@@ -1,4 +0,0 @@
build/logic.o: src/logic.c src/../include/logic.h \
src/../include/globals.h
src/../include/logic.h:
src/../include/globals.h:
BIN
View File
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
build/main.o: src/main.c include/windows.h include/logic.h
include/windows.h:
include/logic.h:
BIN
View File
Binary file not shown.
-4
View File
@@ -1,4 +0,0 @@
build/testLogic.o: src/testLogic.c src/../include/logic.h \
src/../include/globals.h
src/../include/logic.h:
src/../include/globals.h:
BIN
View File
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
build/windows.o: src/windows.c src/../include/windows.h \
src/../include/logic.h src/../include/globals.h
src/../include/windows.h:
src/../include/logic.h:
src/../include/globals.h:
BIN
View File
Binary file not shown.
+2
View File
@@ -6,5 +6,7 @@
#include <string.h> #include <string.h>
extern char productPricesFileLocation[64]; // Dichiarazione extern extern char productPricesFileLocation[64]; // Dichiarazione extern
extern char countFileLocation[64];
extern char configFileLocation[64];
#endif #endif
+68
View File
@@ -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
+165 -3
View File
@@ -5,6 +5,9 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ncurses.h> #include <ncurses.h>
#include "jsonFile.h"
#define PRODUCT_STOCK_UNLIMITED (-1)
struct product { struct product {
float cost; float cost;
@@ -12,8 +15,167 @@ struct product {
char name; char name;
}; };
int getCount(); /*
int existsFile(const char *fname); * Modello dati: nel file prodotti si salva solo la giacenza INIZIALE ("initial").
void createFile(const char *fname); * Lo stock corrente e' sempre calcolato: initial - copie vendute
* (comprese quelle consumate tramite bundle). Le vendite scrivono solo il file vendite.
*/
/* ------------------------------------------------------------------ */
/* 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);
/**
* @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);
/**
* @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);
/**
* @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 */
/* ------------------------------------------------------------------ */
/**
* @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);
/**
* @brief Stampa a schermo (ncurses) le vendite registrate per ciascun prodotto.
*/
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);
/**
* @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 */
/* ------------------------------------------------------------------ */
/**
* @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);
/* ------------------------------------------------------------------ */
/* 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
-20
View File
@@ -1,20 +0,0 @@
#ifndef WINDOWS_H
#define WINDOWS_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
int runMainMenu();
void runOptionsMenu();
void warningf(const char *fmt, ...);
void warning(char *string);
void statusf(const char *fmt, ...);
void status(char *msg);
WINDOW *create_newwin(int height, int width, int starty, int startx);
void destroy_win(WINDOW *local_win);
void print_menu(WINDOW *menu_win, int y, int x, int highlight);
void wwrap_print(WINDOW *win, int start_y, int start_x, const char *str);
#endif
+100
View File
@@ -0,0 +1,100 @@
#ifndef WINDOWSBACK_H
#define WINDOWSBACK_H
#include <stdio.h>
#include <stdlib.h>
#include <string.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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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);
/**
* @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, ...);
/**
* @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);
/**
* @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, ...);
/**
* @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);
#endif
+20
View File
@@ -0,0 +1,20 @@
#ifndef WINDOWSDECLARATION_H
#define WINDOWSDECLARATION_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
int runMainMenu();
void runOptionsMenu();
void runSellWindow();
void runCreateBundleWindow();
void runAboutWindow();
void runViewSalesWindow();
void runViewProductsWindow();
void runAddProductWindow();
void runExportCsvWindow();
void example_window();
#endif
+21 -10
View File
@@ -1,17 +1,21 @@
CC = gcc CC ?= gcc
CFLAGS = -I./include -Wall -Wextra -MMD -MP PKG_CONFIG ?= pkg-config
LDLIBS = -lncurses
CFLAGS = -I./include -Wall -Wextra -MMD -MP $(shell $(PKG_CONFIG) --cflags json-c ncurses)
LDLIBS = $(shell $(PKG_CONFIG) --libs json-c ncurses)
BUNDLE_LDFLAGS = -Wl,--dynamic-linker=/lib64/ld-linux-x86-64.so.2 -Wl,-rpath,'$$ORIGIN/lib'
SRC_DIR = src SRC_DIR = src
BUILD_DIR = build BUILD_DIR = build
SOURCES = $(wildcard $(SRC_DIR)/*.c) SOURCES = $(filter-out $(SRC_DIR)/testLogic.c, $(wildcard $(SRC_DIR)/*.c))
OBJECTS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SOURCES)) OBJECTS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SOURCES))
EXEC = productManager EXEC = productManager
EXECTEST = test
DEPS = $(OBJECTS:.o=.d) DEPS = $(OBJECTS:.o=.d)
all: $(EXEC) all: $(EXEC)
# 1. Per testare in locale su NixOS
$(EXEC): $(OBJECTS) $(EXEC): $(OBJECTS)
$(CC) $^ -o $@ $(LDLIBS) $(CC) $^ -o $@ $(LDLIBS)
@@ -21,10 +25,17 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
-include $(DEPS) -include $(DEPS)
test: # 2. Per creare il pacchetto portabile da inviare
$(CC) $(CFLAGS) src/logic.c src/testLogic.c -o $(EXECTEST) bundle: $(OBJECTS)
@mkdir -p dist/lib
$(CC) $^ -o dist/$(EXEC) $(BUNDLE_LDFLAGS) $(LDLIBS)
@cp -L $$( $(PKG_CONFIG) --variable=libdir json-c )/libjson-c.so* dist/lib/
@cp -L $$( $(PKG_CONFIG) --variable=libdir ncurses )/libncurses*.so* dist/lib/
@tar czf productManager.tar.gz --directory dist/ .
@echo "=== Pacchetto portabile creato con successo in productManager.tar.gz ==="
@rm -fr dist/
clean: clean:
rm -rf $(BUILD_DIR) $(EXEC) rm -rf $(BUILD_DIR) $(EXEC) dist *.json .fpm
.PHONY: all clean .PHONY: all clean bundle
+5 -1
View File
@@ -1,8 +1,12 @@
{ pkgs ? import <nixpkgs> {} }: { pkgs ? import <nixpkgs> {} }:
pkgs.mkShell { pkgs.mkShell {
buildInputs = [ nativeBuildInputs = [
pkgs.gcc pkgs.gcc
pkgs.gnumake pkgs.gnumake
pkgs.pkgconf
];
buildInputs = [
pkgs.ncurses pkgs.ncurses
pkgs.json_c
]; ];
} }
+4 -2
View File
@@ -1,4 +1,6 @@
#include "../include/globals.h" #include "../include/globals.h"
char productPricesFileLocation[64]="./productPricesSettings.fpm"; char productPricesFileLocation[64]="./pm-item-price-default.json";
char countFileLocation[64]="./count.fpm"; char countFileLocation[64]="./pm-item-sold-count-default.json";
char configFileLocation[64]=".fpm";
+113
View File
@@ -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;
}
+788 -37
View File
@@ -2,61 +2,812 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ncurses.h> #include <ncurses.h>
#include <json-c/json.h>
#include "../include/logic.h" #include "../include/logic.h"
#include "../include/globals.h" #include "../include/globals.h"
#include "../include/jsonFile.h"
/* /*
* TODO: Listare prodotti con prezzo da file * Formato dei file (JSON):
* TODO: Aggiungere prodotto con prezzo a file *
* TODO: Listare copie vendute di prodotto da file * File prodotti: [ {"name": "Maglietta S", "price": 19.90, "initial": 12}, ... ]
* TODO: Aggiungere copie venduto di prodotto a file * initial = giacenza INIZIALE (intero), oppure -1 = infinito
* TODO: Cambiare nome dei file con interagisce * bundle = 1, components = [ {"name": "...", "qty": 2}, ... ]
* TODO: Calcolare la somma delle entrate dati i file * 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.
*
* TODO: Aggiungere la possibilita' di registrare gli acquisti non compiuti
* con intenzioni di acquisto.
* e.g. persona vuole acquistare maglietta S ma non c'e' la taglia
*/
char *products; #define MAX_BUNDLE_DEPTH 8
int existsFile(const char *fname) { /* ------------------------------------------------------------------ */
FILE *file; /* Helper JSON */
if ((file = fopen(fname, "r"))) /* ------------------------------------------------------------------ */
{
fclose(file); 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 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 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;
}
/* Scrive prima tutto su file temporanei, poi li rinomina. sales puo' essere NULL. */
static int commitFiles(const char *productsFile, json_object *products,
const char *soldFile, json_object *sales) {
char tmpP[512], tmpS[512];
if (writeTmp(productsFile, products, tmpP, sizeof tmpP) != 0)
return -1;
if (sales) {
if (writeTmp(soldFile, sales, tmpS, sizeof tmpS) != 0) {
remove(tmpP);
return -1;
}
}
if (rename(tmpP, productsFile) != 0) {
remove(tmpP);
if (sales)
remove(tmpS);
return -1;
}
if (sales && rename(tmpS, soldFile) != 0) {
remove(tmpS);
return -1;
}
return 0;
}
/* ------------------------------------------------------------------ */
/* Configurazione */
/* ------------------------------------------------------------------ */
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;
}
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);
}
readConfigString(loaded, "productsFile", productPricesFileLocation, sizeof productPricesFileLocation);
readConfigString(loaded, "salesFile", countFileLocation, sizeof countFileLocation);
json_object_put(loaded);
return 0;
}
/* ------------------------------------------------------------------ */
/* Prodotti */
/* ------------------------------------------------------------------ */
/* Array JSON dei prodotti (da liberare con json_object_put()), NULL se errore. */
json_object *getProducts(const char *fname) {
return loadJsonArray(fname);
}
/*
* Come getProducts, ma ogni prodotto semplice ha in piu' un campo "stock"
* 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) {
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)
return -1;
if (findItemByName(arr, name)) {
json_object_put(arr);
return 1; 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, "initial",
json_object_new_int(stock < 0 ? PRODUCT_STOCK_UNLIMITED : stock));
json_object_array_add(arr, item);
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);
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 = loadJsonArray(fname);
if (!arr)
return -1.0;
double price = -1.0;
json_object *item = findItemByName(arr, name);
json_object *jprice;
if (item && json_object_object_get_ex(item, "price", &jprice))
price = json_object_get_double(jprice);
json_object_put(arr);
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; return 0;
} }
void createFile(const char *fname) { /*
FILE *file; * Modifica un prodotto. "stock" e' la giacenza ATTUALE desiderata
file = fopen(fname, "w"); * (negativo = infinita): internamente diventa initial = stock + gia' consumato.
fclose(file); * 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;
char* getProducts(const char *fname) { json_object *item = findItemByName(products, oldName);
/* FILE *file; if (!item) {
json_object_put(products);
if((file=fopen(fname, "r"))==NULL) { return 1;
printf("Errore nell'apertura del file");
exit(1);
} }
// TODO QUESTO CODICE NON E' MIO, RICONTROLLARE FUNZIONAMENTO fread() int renamed = strcmp(oldName, newName) != 0;
if (renamed && findItemByNameExcluding(products, newName, item)) {
while(!feof(file)){ json_object_put(products);
n = fread(buffer, sizeof(char), sizeof(buffer), file); return 2;
for (i=0; i<n; i++)
if (buffer[i] == c)
count++;
} }
fclose(file);
*/ 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 getCount(){ 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 */
/* ------------------------------------------------------------------ */
/* 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 = loadJsonArray(fname);
if (!arr)
return -1;
int count = soldOf(arr, name);
json_object_put(arr);
return count;
}
/* Stampa a schermo (ncurses) le copie vendute di ogni prodotto. */
void listSold(const char *fname) {
json_object *arr = loadJsonArray(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("Nessuna vendita registrata.\n");
for (size_t i = 0; i < n; i++) {
json_object *item = json_object_array_get_idx(arr, i);
json_object *jname, *jsold;
if (json_object_object_get_ex(item, "name", &jname) &&
json_object_object_get_ex(item, "sold", &jsold))
printw("%zu) %-30s %5d\n", i + 1,
json_object_get_string(jname),
json_object_get_int(jsold));
}
refresh();
json_object_put(arr);
}
/* Aggiunge "qty" copie vendute (crea la voce se non esiste). 0 ok, -1 errore. */
int addSold(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_object_add(item, "sold", json_object_new_int(soldOf(arr, name) + qty));
} else {
item = json_object_new_object();
json_object_object_add(item, "name", json_object_new_string(name));
json_object_object_add(item, "sold", json_object_new_int(qty));
json_object_array_add(arr, item);
}
int res = saveJsonArray(fname, arr);
json_object_put(arr);
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 */
/* ------------------------------------------------------------------ */
/* Somma di (prezzo * copie vendute). Ritorna -1.0 in caso di errore. */
double getTotalRevenue(const char *productsFile, const char *soldFile) {
json_object *products = loadJsonArray(productsFile);
if (!products)
return -1.0;
json_object *sales = loadJsonArray(soldFile);
if (!sales) {
json_object_put(products);
return -1.0;
}
double total = 0.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);
json_object *jname, *jsold, *jprice;
if (!json_object_object_get_ex(sale, "name", &jname) ||
!json_object_object_get_ex(sale, "sold", &jsold))
continue;
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);
}
json_object_put(products);
json_object_put(sales);
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; return 0;
} }
+9 -23
View File
@@ -19,8 +19,10 @@
#include <string.h> #include <string.h>
#include <ncurses.h> #include <ncurses.h>
#include <windows.h> #include "../include/windowsDeclaration.h"
#include <logic.h> #include "../include/windowsBack.h"
#include "../include/logic.h"
#include "../include/globals.h"
void printLogoBG(); void printLogoBG();
@@ -42,31 +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; printLogoBG();
while(running != 6){
refresh(); refresh();
running = runMainMenu();
switch(running) {
case 1:
warning("Questa e' una stringa molto lunga . . . Ma anche un test, che ti devo dire, ci proviamo a fare cose che non sono mai state fatte, oltretutto che divertimento ci sarebbe senza esperienze nuove?");
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
runOptionsMenu();
break;
case 6:
break;
}
} }
endwin(); endwin();
@@ -75,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");
} }
+73 -7
View File
@@ -6,12 +6,78 @@
#include "../include/logic.h" #include "../include/logic.h"
#include "../include/globals.h" #include "../include/globals.h"
int main() { #define PRODUCTS_FILE "test_products.fpm"
/* Working #define SOLD_FILE "test_sold.fpm"
printf("%d", existsFile("qwestoeuntest")); #define RENAMED_FILE "test_renamed.fpm"
createFile("qwestoeuntest");
printf("%d", existsFile("qwestoeuntest"));
*/
return 0; static int passed = 0;
static int failed = 0;
#define CHECK(cond, desc) do { \
if (cond) { passed++; printf(" [ OK ] %s\n", desc); } \
else { failed++; printf(" [FAIL] %s (riga %d)\n", desc, __LINE__); } \
} while (0)
static int near(double a, double b) {
return (a - b) < 1e-9 && (b - a) < 1e-9;
}
static void cleanup(void) {
remove(PRODUCTS_FILE);
remove(SOLD_FILE);
remove(RENAMED_FILE);
}
int main(void) {
cleanup();
printf("== File ==\n");
CHECK(existsFile(PRODUCTS_FILE) == 0, "il file non esiste all'inizio");
CHECK(createFile(PRODUCTS_FILE) == 0, "createFile ritorna 0");
CHECK(existsFile(PRODUCTS_FILE) == 1, "il file esiste dopo createFile");
/* File completamente vuoto (non JSON): deve essere trattato come array vuoto */
FILE *f = fopen(SOLD_FILE, "w");
if (f) fclose(f);
CHECK(getCount(SOLD_FILE, "Qualsiasi") == 0, "file vuoto trattato come lista vuota");
printf("== Prodotti ==\n");
CHECK(addProduct(PRODUCTS_FILE, "Maglietta S", 20.00) == 0, "aggiunta Maglietta S");
CHECK(addProduct(PRODUCTS_FILE, "Pantaloni", 35.50) == 0, "aggiunta Pantaloni");
CHECK(addProduct(PRODUCTS_FILE, "Maglietta S", 99.99) == 1, "duplicato rifiutato (ritorna 1)");
CHECK(near(getPrice(PRODUCTS_FILE, "Maglietta S"), 20.00), "prezzo Maglietta S = 20.00");
CHECK(near(getPrice(PRODUCTS_FILE, "Pantaloni"), 35.50), "prezzo Pantaloni = 35.50");
CHECK(near(getPrice(PRODUCTS_FILE, "Inesistente"), -1.0), "prodotto inesistente -> -1.0");
json_object *arr = getProducts(PRODUCTS_FILE);
CHECK(arr != NULL && json_object_array_length(arr) == 2, "getProducts ritorna 2 prodotti");
if (arr) json_object_put(arr);
printf("== Copie vendute ==\n");
CHECK(getCount(SOLD_FILE, "Maglietta S") == 0, "nessuna vendita all'inizio");
CHECK(addSold(SOLD_FILE, "Maglietta S", 2) == 0, "addSold Maglietta S +2");
CHECK(addSold(SOLD_FILE, "Maglietta S", 1) == 0, "addSold Maglietta S +1");
CHECK(addSold(SOLD_FILE, "Pantaloni", 2) == 0, "addSold Pantaloni +2");
CHECK(getCount(SOLD_FILE, "Maglietta S") == 3, "Maglietta S: 3 copie");
CHECK(getCount(SOLD_FILE, "Pantaloni") == 2, "Pantaloni: 2 copie");
CHECK(getCount(SOLD_FILE, "Inesistente") == 0, "prodotto mai venduto: 0 copie");
printf("== Entrate ==\n");
/* 3 * 20.00 + 2 * 35.50 = 131.00 */
CHECK(near(getTotalRevenue(PRODUCTS_FILE, SOLD_FILE), 131.00), "entrate totali = 131.00");
/* Una vendita di un prodotto non presente nel listino non deve contare */
addSold(SOLD_FILE, "Fantasma", 10);
CHECK(near(getTotalRevenue(PRODUCTS_FILE, SOLD_FILE), 131.00), "vendite senza prezzo ignorate");
printf("== Rinomina ==\n");
CHECK(renameDataFile(PRODUCTS_FILE, RENAMED_FILE) == 0, "renameDataFile ritorna 0");
CHECK(existsFile(PRODUCTS_FILE) == 0 && existsFile(RENAMED_FILE) == 1, "vecchio nome sparito, nuovo presente");
CHECK(near(getPrice(RENAMED_FILE, "Pantaloni"), 35.50), "i dati sopravvivono alla rinomina");
CHECK(renameDataFile("non_esiste.fpm", "altro.fpm") == -1, "rinomina di file inesistente fallisce");
cleanup();
printf("\nRisultato: %d ok, %d falliti\n", passed, failed);
return failed == 0 ? 0 : 1;
} }
-276
View File
@@ -1,276 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include "../include/windows.h"
#include "../include/logic.h"
#include "../include/globals.h"
#define MAX_LEN 64
char *mainMenu[] = {
"Segna vendita",
"Vedi vendite",
"Vedi prodotti",
"Gestisci prodotti",
"Opzioni",
"Exit"
};
int n_mainMenu = sizeof(mainMenu) / sizeof(char *);
int runMainMenu(){
WINDOW *mainWin;
int startx, starty, width, height;
height = LINES / 2;
width = COLS / 2;
starty = 0;
startx = 0;
mainWin = create_newwin(height, width, starty, startx);
int highlight = 1;
int choice = 0;
int c;
keypad(mainWin, TRUE);
wwrap_print(mainWin, 1, 2, "Usa le FRECCETTE ed INVIO per scegliere un'opzione,\nin alternativa ci sono i movement di vim e SPAZIO.");
refresh();
print_menu(mainWin, height / 2 - 3, width / 2 - 9, highlight);
while(1)
{ c = wgetch(mainWin);
switch(c)
{ case KEY_UP:
case KEY_LEFT:
case 'h':
case 'k':
if(highlight == 1)
highlight = n_mainMenu;
else
--highlight;
break;
case KEY_DOWN:
case KEY_RIGHT:
case 'l':
case 'j':
if(highlight == n_mainMenu)
highlight = 1;
else
++highlight;
break;
case 10:
case 32:
choice = highlight;
break;
default:
statusf("Il tasto '%c' non e' un tasto valido", c);
refresh();
break;
}
print_menu(mainWin, height / 2 - 3, width / 2 - 9, highlight);
if(choice != 0) /* User did a choice come out of the infinite loop */
break;
}
statusf("Avvio opzione '%s'", mainMenu[choice - 1]);
clrtoeol();
refresh();
destroy_win(mainWin);
return choice;
}
void runOptionsMenu(){
WINDOW *optionsWin;
int startx, starty, width, height;
height = LINES / 2;
width = COLS / 2;
starty = 0;
startx = COLS / 2;
optionsWin = create_newwin(height, width, starty, startx);
char input[MAX_LEN + 1];
strcpy(input, productPricesFileLocation);
int ch, pos = strlen(input);
mvwprintw(optionsWin, 1, 2, "Posizione file prodotti:");
wattron(optionsWin, COLOR_PAIR(2));
for (int i = 0; i < MAX_LEN; i++) {
mvwaddch(optionsWin, 2, 2 + i, ' ');
}
mvwprintw(optionsWin, 2, 2, "%s", input);
wmove(optionsWin, 2, 2 + strlen(input));
wrefresh(optionsWin);
pos = strlen(input);
// Input nella finestra
curs_set(1);
while ((ch = wgetch(optionsWin)) != '\n') {
if (ch == KEY_BACKSPACE || ch == 127 || ch == '\b') {
if (pos > 0) {
pos--;
input[pos] = '\0';
mvwaddch(optionsWin, 2, 2 + pos, ' ');
wmove(optionsWin, 2, 2 + pos);
}
} else if (pos < MAX_LEN && ch >= 32 && ch <= 126) {
input[pos++] = ch;
input[pos] = '\0';
mvwaddch(optionsWin, 2, 2 + pos - 1, ch);
}
wrefresh(optionsWin);
}
curs_set(0);
wattroff(optionsWin, COLOR_PAIR(2));
if (exists(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);
}
werase(optionsWin);
destroy_win(optionsWin);
}
void warningf(const char *fmt, ...) {
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
warning(buf);
}
void warning(char *string){
WINDOW *warningWin;
int startx, starty, width, height;
height = LINES / 2 - 2;
width = COLS / 2;
starty = LINES / 2;
startx = 0;
warningWin = create_newwin(height, width, starty, startx);
wbkgd(warningWin, COLOR_PAIR(1));
mvwprintw(warningWin, 2, 2, "ATTENZIONE!!! E' avvenuto un errore! ");
wwrap_print(warningWin, 4, 3, string);
mvwprintw(warningWin, height - 1, 3, " Premi un tasto per continuare . . . ");
wrefresh(warningWin);
getch();
werase(warningWin);
destroy_win(warningWin);
}
void statusf(const char *fmt, ...) {
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
status(buf);
}
void status(char *msg) {
attron(COLOR_PAIR(3));
mvprintw(LINES -2, 0, "%-*s", COLS, msg);
attroff(COLOR_PAIR(3));
refresh();
}
WINDOW *create_newwin(int height, int width, int starty, int startx)
{ WINDOW *local_win;
local_win = newwin(height, width, starty, startx);
box(local_win, 0 , 0);
wrefresh(local_win);
return local_win;
}
void destroy_win(WINDOW *local_win)
{
wborder(local_win, ':', ':', '.', '.', '.', '.', '.', '.');
wrefresh(local_win);
delwin(local_win);
}
void print_menu(WINDOW *menu_win, int y, int x, int highlight)
{
int i;
box(menu_win, 0, 0);
for(i = 0; i < n_mainMenu; ++i)
{ if(highlight == i + 1) /* High light the present choice */
{ wattron(menu_win, A_REVERSE);
mvwprintw(menu_win, y, x, "%s", mainMenu[i]);
wattroff(menu_win, A_REVERSE);
}
else
mvwprintw(menu_win, y, x, "%s", mainMenu[i]);
++y;
}
wrefresh(menu_win);
}
void wwrap_print(WINDOW *win, int start_y, int start_x, const char *str) {
int max_width = getmaxx(win) - (start_y + 1);
int y = start_y;
int x = start_x;
int line_length = 0;
int word_length = 0;
const char *word_start = str;
while (*str) {
// Trova la prossima parola
if (*str != ' ' && *str != '\n' && *str != '\t') {
str++;
continue;
}
// Calcola lunghezza parola corrente
word_length = str - word_start;
// Gestione wrap
if (line_length + word_length > max_width) {
y++;
x = start_x;
line_length = 0;
}
// Stampa parola
for (int i = 0; i < word_length; i++) {
mvwaddch(win, y, x++, word_start[i]);
}
line_length += word_length;
// Gestione spazi
if (*str == ' ' || *str == '\t') {
if (line_length < max_width) {
mvwaddch(win, y, x++, ' ');
line_length++;
} else {
y++;
x = start_x;
line_length = 0;
}
} else if (*str == '\n') {
y++;
x = start_x;
line_length = 0;
}
word_start = ++str;
}
// Stampa ultima parola
word_length = strlen(word_start);
if (line_length + word_length > max_width) {
y++;
x = start_x;
}
mvwprintw(win, y, x, "%s", word_start);
}
+205
View File
@@ -0,0 +1,205 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ncurses.h>
#include "../include/windowsBack.h"
#include "../include/logic.h"
#include "../include/globals.h"
#define MAX_LEN 64
// Scrivi in una finestra di warning
void warningf(const char *fmt, ...) {
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
warning(buf);
}
// Dichiara una finestra di warning, avviato da warningf
void warning(char *string){
WINDOW *warningWin;
int startx, starty, width, height;
height = LINES / 2 - 2;
width = COLS / 2;
starty = LINES / 2;
startx = 0;
warningWin = create_newwin(height, width, starty, startx);
wbkgd(warningWin, COLOR_PAIR(1));
mvwprintw(warningWin, 2, 2, "ATTENZIONE!!! E' avvenuto un errore! ");
wwrap_print(warningWin, 4, 3, string);
mvwprintw(warningWin, height - 1, 3, " Premi un tasto per continuare . . . ");
wrefresh(warningWin);
getch();
werase(warningWin);
destroy_win(warningWin);
}
// Scrivi nella barra di status
void statusf(const char *fmt, ...) {
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
status(buf);
}
// Posiziona il testo indicato in statusf nella barra di status
void status(char *msg) {
attron(COLOR_PAIR(3));
mvprintw(LINES -2, 0, "%-*s", COLS, msg);
attroff(COLOR_PAIR(3));
refresh();
}
WINDOW *create_newwin(int height, int width, int starty, int startx)
{ WINDOW *local_win;
local_win = newwin(height, width, starty, startx);
box(local_win, 0 , 0);
wrefresh(local_win);
return local_win;
}
void destroy_win(WINDOW *local_win)
{
//wborder(local_win, ':', ':', '.', '.', '.', '.', '.', '.');
wrefresh(local_win);
delwin(local_win);
}
// Scrive in una finestra facendo il wrapping a parola del testo in modo che non esca dalla finestra
void wwrap_print(WINDOW *win, int start_y, int start_x, const char *str) {
int max_width = getmaxx(win) - (start_y + 1);
int y = start_y;
int x = start_x;
int line_length = 0;
int word_length = 0;
const char *word_start = str;
while (*str) {
// Trova la prossima parola
if (*str != ' ' && *str != '\n' && *str != '\t') {
str++;
continue;
}
// Calcola lunghezza parola corrente
word_length = str - word_start;
// Gestione wrap
if (line_length + word_length > max_width) {
y++;
x = start_x;
line_length = 0;
}
// Stampa parola
for (int i = 0; i < word_length; i++) {
mvwaddch(win, y, x++, word_start[i]);
}
line_length += word_length;
// Gestione spazi
if (*str == ' ' || *str == '\t') {
if (line_length < max_width) {
mvwaddch(win, y, x++, ' ');
line_length++;
} else {
y++;
x = start_x;
line_length = 0;
}
} else if (*str == '\n') {
y++;
x = start_x;
line_length = 0;
}
word_start = ++str;
}
// Stampa ultima parola
word_length = strlen(word_start);
if (line_length + word_length > max_width) {
y++;
x = start_x;
}
mvwprintw(win, y, x, "%s", word_start);
}
// Elemento di UI; Menu a scelta multipla
int uiSelectMenu(WINDOW *win, int start_y, int start_x, char **options, int n_options) {
int highlight = 1, choice = 0, c;
keypad(win, TRUE);
while(1) {
for(int i = 0; i < n_options; ++i) {
if(highlight == i + 1) wattron(win, A_REVERSE);
mvwprintw(win, start_y + i, start_x, "%s", options[i]);
if(highlight == i + 1) wattroff(win, A_REVERSE);
}
wrefresh(win);
c = wgetch(win);
switch(c) {
case KEY_UP: case 'k': highlight = (highlight == 1) ? n_options : highlight - 1; break;
case KEY_DOWN: case 'j': highlight = (highlight == n_options) ? 1 : highlight + 1; break;
case 10: case 32: choice = highlight; break;
}
if(choice != 0) break;
}
return choice;
}
// Elemento di UI; Campo di inserimento del testo
void uiTextInput(WINDOW *win, int y, int x, char *input, int max_len) {
int ch, pos = strlen(input);
wattron(win, COLOR_PAIR(2));
curs_set(1);
for(int i = 0; i < max_len; i++) mvwaddch(win, y, x + i, ' ');
mvwprintw(win, y, x, "%s", input);
wmove(win, y, x + pos);
wrefresh(win);
while ((ch = wgetch(win)) != '\n') {
if (ch == KEY_BACKSPACE || ch == 127 || ch == '\b') {
if (pos > 0) {
pos--;
input[pos] = '\0';
mvwaddch(win, y, x + pos, ' ');
wmove(win, y, x + pos);
}
} else if (pos < max_len && ch >= 32 && ch <= 126) {
input[pos++] = ch;
input[pos] = '\0';
mvwaddch(win, y, x + pos - 1, ch);
}
wrefresh(win);
}
curs_set(0);
wattroff(win, COLOR_PAIR(2));
}
// Elemento di UI; Tasto a 2 stati (ON/OFF)
int uiToggleSwitch(WINDOW *win, int y, int x, int state) {
int c;
keypad(win, TRUE);
while(1) {
wattron(win, A_REVERSE);
mvwprintw(win, y, x, state ? "[ ON ] " : "[ OFF ]");
wattroff(win, A_REVERSE);
wrefresh(win);
c = wgetch(win);
if (c == 10 || c == 32) break;
if (c == KEY_LEFT || c == KEY_RIGHT || c == 'h' || c == 'j' || c == 'k' || c == 'l') state = !state;
}
mvwprintw(win, y, x, state ? "[ ON ] " : "[ OFF ]");
wrefresh(win);
return state;
}
File diff suppressed because it is too large. Load diff