aggiunta logica di gestione dati. Assistito con ai
This commit is contained in:
1 parent
1a15d93f76
commit
5db402c7cf
10 files changed
+769
-71
No files matched your search
@@ -6,5 +6,6 @@
|
||||
#include <string.h>
|
||||
|
||||
extern char productPricesFileLocation[64]; // Dichiarazione extern
|
||||
extern char countFileLocation[64];
|
||||
|
||||
#endif
|
||||
+22
-5
@@ -1,19 +1,36 @@
|
||||
#ifndef LOGIC_H
|
||||
#define LOGIC_H
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ncurses.h>
|
||||
#include <json-c/json.h>
|
||||
|
||||
struct product {
|
||||
float cost;
|
||||
int numberSold;
|
||||
char name;
|
||||
float cost;
|
||||
int numberSold;
|
||||
char name;
|
||||
};
|
||||
|
||||
int getCount();
|
||||
/* File */
|
||||
int existsFile(const char *fname);
|
||||
void createFile(const char *fname);
|
||||
int createFile(const char *fname);
|
||||
int renameDataFile(const char *oldName, const char *newName);
|
||||
|
||||
/* Prodotti */
|
||||
json_object *getProducts(const char *fname);
|
||||
void listProducts(const char *fname);
|
||||
int addProduct(const char *fname, const char *name, double price);
|
||||
double getPrice(const char *fname, const char *name);
|
||||
|
||||
/* Copie vendute */
|
||||
int getCount(const char *fname, const char *name);
|
||||
void listSold(const char *fname);
|
||||
int addSold(const char *fname, const char *name, int qty);
|
||||
|
||||
/* Entrate */
|
||||
double getTotalRevenue(const char *productsFile, const char *soldFile);
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
int runMainMenu();
|
||||
void runOptionsMenu();
|
||||
void runSellWindow();
|
||||
void runAboutWindow();
|
||||
void runViewSalesWindow();
|
||||
void runViewProductsWindow();
|
||||
void runAddProductWindow();
|
||||
void example_window();
|
||||
|
||||
#endif
|
||||
@@ -1,28 +1,39 @@
|
||||
CC = gcc
|
||||
CFLAGS = -I./include -Wall -Wextra -MMD -MP
|
||||
LDLIBS = -lncurses
|
||||
CC ?= gcc
|
||||
PKG_CONFIG ?= pkg-config
|
||||
|
||||
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
|
||||
BUILD_DIR = build
|
||||
SOURCES = $(filter-out $(SRC_DIR)/testLogic.c, $(wildcard $(SRC_DIR)/*.c))
|
||||
OBJECTS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SOURCES))
|
||||
TEST_SOURCES = $(SRC_DIR)/logic.c $(SRC_DIR)/testLogic.c
|
||||
TEST_OBJECTS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(TEST_SOURCES))
|
||||
EXEC = productManager
|
||||
EXECTEST = test
|
||||
DEPS = $(OBJECTS:.o=.d)
|
||||
|
||||
all: $(EXEC)
|
||||
|
||||
# 1. Per testare in locale su NixOS
|
||||
$(EXEC): $(OBJECTS)
|
||||
$(CC) $^ -o $@ $(LDLIBS)
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
|
||||
@mkdir -p $(@D)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
-include $(DEPS)
|
||||
|
||||
test: $(TEST_OBJECTS)
|
||||
$(CC) $^ -o $(EXECTEST) $(LDLIBS)
|
||||
# 2. Per creare il pacchetto portabile da inviare
|
||||
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/
|
||||
@echo "=== Pacchetto portabile creato con successo in ./dist ==="
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR) $(EXEC) $(EXECTEST)
|
||||
rm -rf $(BUILD_DIR) $(EXEC) dist *.json
|
||||
|
||||
.PHONY: all clean
|
||||
.PHONY: all clean bundle
|
||||
@@ -1,8 +1,12 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.gcc
|
||||
pkgs.gnumake
|
||||
pkgs.ncurses
|
||||
];
|
||||
nativeBuildInputs = [
|
||||
pkgs.gcc
|
||||
pkgs.gnumake
|
||||
pkgs.pkgconf
|
||||
];
|
||||
buildInputs = [
|
||||
pkgs.ncurses
|
||||
pkgs.json_c
|
||||
];
|
||||
}
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
#include "../include/globals.h"
|
||||
|
||||
char productPricesFileLocation[64]="./productPricesSettings.fpm";
|
||||
char countFileLocation[64]="./count.fpm";
|
||||
char productPricesFileLocation[64]="./pm-item-price-default.json";
|
||||
char countFileLocation[64]="./pm-item-sold-count-default.json";
|
||||
|
||||
+274
-38
@@ -2,64 +2,300 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ncurses.h>
|
||||
#include <json-c/json.h>
|
||||
|
||||
#include "../include/logic.h"
|
||||
#include "../include/globals.h"
|
||||
|
||||
/*
|
||||
* TODO: Listare prodotti con prezzo da file
|
||||
* TODO: Aggiungere prodotto con prezzo a file
|
||||
* TODO: Listare copie vendute di prodotto da file
|
||||
* TODO: Aggiungere copie venduto di prodotto a file
|
||||
* TODO: Cambiare nome dei file con interagisce
|
||||
* TODO: Calcolare la somma delle entrate dati i file
|
||||
* TODO: Aggiungere la possibilità di aggiungere gli acquisti non compiuti con intenzioni di acquisto.
|
||||
* e.g. persona vuole acquistare maglietta S ma non c'è la taglia
|
||||
*/
|
||||
* Formato dei file (JSON):
|
||||
*
|
||||
* File prodotti: [ {"name": "Maglietta S", "price": 19.90}, ... ]
|
||||
* File vendite: [ {"name": "Maglietta S", "sold": 12}, ... ]
|
||||
*
|
||||
* Compilazione: aggiungere -ljson-c (e -lncurses) al linker.
|
||||
*
|
||||
* TODO: Aggiungere la possibilità di registrare gli acquisti non compiuti
|
||||
* con intenzioni di acquisto.
|
||||
* e.g. persona vuole acquistare maglietta S ma non c'è la taglia
|
||||
*/
|
||||
|
||||
char *products;
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Utilità sui file */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int existsFile(const char *fname) {
|
||||
FILE *file;
|
||||
if ((file = fopen(fname, "r")))
|
||||
{
|
||||
FILE *file = fopen(fname, "r");
|
||||
if (file) {
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void createFile(const char *fname) {
|
||||
FILE *file;
|
||||
file = fopen(fname, "w");
|
||||
fclose(file);
|
||||
/* Crea un file JSON contenente un array vuoto. Ritorna 0 se ok, -1 se errore. */
|
||||
int createFile(const char *fname) {
|
||||
json_object *arr = json_object_new_array();
|
||||
if (!arr)
|
||||
return -1;
|
||||
|
||||
int res = json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY);
|
||||
json_object_put(arr);
|
||||
return (res == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
char* getProducts(const char *fname) {
|
||||
/* FILE *file;
|
||||
|
||||
if((file=fopen(fname, "r"))==NULL) {
|
||||
printf("Errore nell'apertura del file");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// TODO QUESTO CODICE NON E' MIO, RICONTROLLARE FUNZIONAMENTO fread()
|
||||
|
||||
while(!feof(file)){
|
||||
n = fread(buffer, sizeof(char), sizeof(buffer), file);
|
||||
for (i=0; i<n; i++)
|
||||
if (buffer[i] == c)
|
||||
count++;
|
||||
}
|
||||
fclose(file);
|
||||
*/
|
||||
return 0;
|
||||
/* Cambia il nome di un file di dati. Ritorna 0 se ok, -1 se errore. */
|
||||
int renameDataFile(const char *oldName, const char *newName) {
|
||||
return (rename(oldName, newName) == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
int getCount(){
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Funzioni interne */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
return 0;
|
||||
/*
|
||||
* Carica l'array JSON dal file (creandolo se non esiste).
|
||||
* Il chiamante deve liberare il risultato con json_object_put().
|
||||
* Ritorna NULL in caso di errore o se il contenuto non è un array.
|
||||
*/
|
||||
static json_object *loadArray(const char *fname) {
|
||||
if (!existsFile(fname) && createFile(fname) != 0)
|
||||
return NULL;
|
||||
|
||||
/* Un file vuoto (e.g. creato a mano o dalla vecchia createFile)
|
||||
* non e' JSON valido: lo trattiamo come un array vuoto. */
|
||||
FILE *f = fopen(fname, "r");
|
||||
if (!f)
|
||||
return NULL;
|
||||
int empty = (fgetc(f) == EOF);
|
||||
fclose(f);
|
||||
if (empty)
|
||||
return json_object_new_array();
|
||||
|
||||
json_object *arr = json_object_from_file(fname);
|
||||
if (!arr)
|
||||
return NULL;
|
||||
|
||||
if (!json_object_is_type(arr, json_type_array)) {
|
||||
json_object_put(arr);
|
||||
return NULL;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/* Cerca un elemento per nome. Ritorna l'oggetto (non va liberato) o NULL. */
|
||||
static json_object *findByName(json_object *arr, const char *name) {
|
||||
size_t n = json_object_array_length(arr);
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
json_object *item = json_object_array_get_idx(arr, i);
|
||||
json_object *jname;
|
||||
|
||||
if (json_object_object_get_ex(item, "name", &jname) &&
|
||||
strcmp(json_object_get_string(jname), name) == 0)
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Prodotti */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* Ritorna l'array JSON dei prodotti (da liberare con json_object_put()),
|
||||
* oppure NULL in caso di errore.
|
||||
*/
|
||||
json_object *getProducts(const char *fname) {
|
||||
return loadArray(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.
|
||||
* Ritorna 0 se ok, -1 se errore, 1 se il prodotto esiste già.
|
||||
*/
|
||||
int addProduct(const char *fname, const char *name, double price) {
|
||||
json_object *arr = loadArray(fname);
|
||||
if (!arr)
|
||||
return -1;
|
||||
|
||||
if (findByName(arr, name)) {
|
||||
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_array_add(arr, item);
|
||||
|
||||
int res = json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY);
|
||||
json_object_put(arr);
|
||||
return (res == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Ritorna il prezzo di un prodotto, oppure -1.0 se non trovato/errore. */
|
||||
double getPrice(const char *fname, const char *name) {
|
||||
json_object *arr = loadArray(fname);
|
||||
if (!arr)
|
||||
return -1.0;
|
||||
|
||||
double price = -1.0;
|
||||
json_object *item = findByName(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;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Copie vendute */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Ritorna le copie vendute di un prodotto (0 se non presente, -1 se errore). */
|
||||
int getCount(const char *fname, const char *name) {
|
||||
json_object *arr = loadArray(fname);
|
||||
if (!arr)
|
||||
return -1;
|
||||
|
||||
int count = 0;
|
||||
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);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Stampa a schermo (ncurses) le copie vendute di ogni prodotto. */
|
||||
void listSold(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("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 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) {
|
||||
json_object *arr = loadArray(fname);
|
||||
if (!arr)
|
||||
return -1;
|
||||
|
||||
json_object *item = findByName(arr, name);
|
||||
|
||||
if (item) {
|
||||
json_object *jsold;
|
||||
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 {
|
||||
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 = json_object_to_file_ext(fname, arr, JSON_C_TO_STRING_PRETTY);
|
||||
json_object_put(arr);
|
||||
return (res == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Entrate */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
json_object *products = loadArray(productsFile);
|
||||
if (!products)
|
||||
return -1.0;
|
||||
|
||||
json_object *sales = loadArray(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 = findByName(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;
|
||||
}
|
||||
+6
-3
@@ -55,17 +55,20 @@ int main(){
|
||||
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?");
|
||||
runSellWindow();
|
||||
break;
|
||||
case 2:
|
||||
runViewSalesWindow();
|
||||
break;
|
||||
case 3:
|
||||
runViewProductsWindow();
|
||||
break;
|
||||
case 4:
|
||||
example_window();
|
||||
runAddProductWindow();
|
||||
break;
|
||||
case 5:
|
||||
runOptionsMenu();
|
||||
runAboutWindow();
|
||||
// runOptionsMenu();
|
||||
break;
|
||||
case 6:
|
||||
break;
|
||||
|
||||
+74
-6
@@ -6,10 +6,78 @@
|
||||
#include "../include/logic.h"
|
||||
#include "../include/globals.h"
|
||||
|
||||
int main() {
|
||||
printf("%d", existsFile("qwestoeuntest"));
|
||||
createFile("qwestoeuntest");
|
||||
printf("%d", existsFile("qwestoeuntest"));
|
||||
|
||||
return 0;
|
||||
#define PRODUCTS_FILE "test_products.fpm"
|
||||
#define SOLD_FILE "test_sold.fpm"
|
||||
#define RENAMED_FILE "test_renamed.fpm"
|
||||
|
||||
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;
|
||||
}
|
||||
+354
-2
@@ -8,6 +8,147 @@
|
||||
#include "../include/logic.h"
|
||||
#include "../include/globals.h"
|
||||
#define MAX_LEN 64
|
||||
#define LINE_LEN 96
|
||||
|
||||
/*
|
||||
*
|
||||
* HELPER
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
static void freeLines(char **lines, int n) {
|
||||
for (int i = 0; i < n; i++)
|
||||
free(lines[i]);
|
||||
free(lines);
|
||||
}
|
||||
|
||||
// Legge un campo stringa da un elemento JSON ("?" se manca)
|
||||
static const char *jsonStr(json_object *item, const char *key) {
|
||||
json_object *v = NULL;
|
||||
const char *s = NULL;
|
||||
if (json_object_object_get_ex(item, key, &v) && v)
|
||||
s = json_object_get_string(v);
|
||||
return s ? s : "?";
|
||||
}
|
||||
|
||||
static double jsonDouble(json_object *item, const char *key) {
|
||||
json_object *v = NULL;
|
||||
return (json_object_object_get_ex(item, key, &v) && v) ? json_object_get_double(v) : 0.0;
|
||||
}
|
||||
|
||||
static int jsonInt(json_object *item, const char *key) {
|
||||
json_object *v = NULL;
|
||||
return (json_object_object_get_ex(item, key, &v) && v) ? json_object_get_int(v) : 0;
|
||||
}
|
||||
|
||||
// Costruisce le righe "nome prezzo" dall'array dei prodotti.
|
||||
// *n contiene il numero di righe create. Ritorna NULL se malloc fallisce.
|
||||
static char **buildProductLines(json_object *products, int *n) {
|
||||
int count = (int)json_object_array_length(products);
|
||||
char **lines = malloc((count > 0 ? count : 1) * sizeof *lines);
|
||||
if (!lines) {
|
||||
*n = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int made = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
json_object *item = json_object_array_get_idx(products, i);
|
||||
lines[i] = malloc(LINE_LEN);
|
||||
if (!lines[i])
|
||||
break;
|
||||
snprintf(lines[i], LINE_LEN, "%-26.26s %8.2f",
|
||||
jsonStr(item, "name"), jsonDouble(item, "price"));
|
||||
made++;
|
||||
}
|
||||
*n = made;
|
||||
return lines;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finestra a elenco scorrevole.
|
||||
* - selectable = 1: ritorna l'indice scelto con INVIO/SPAZIO, -1 se annullato (q)
|
||||
* - selectable = 0: sola lettura, ritorna sempre -1
|
||||
* "footer" (puo' essere NULL) e' una riga di testo fissa sopra i comandi.
|
||||
*/
|
||||
static int listWindow(const char *title, char **lines, int n,
|
||||
const char *footer, int selectable) {
|
||||
int height = LINES - 4;
|
||||
int width = COLS / 2;
|
||||
int visible = height - 6;
|
||||
int textw = width - 4;
|
||||
if (visible < 1) visible = 1;
|
||||
if (textw < 0) textw = 0;
|
||||
|
||||
WINDOW *win = create_newwin(height, width, 0, COLS / 2);
|
||||
keypad(win, TRUE);
|
||||
mvwprintw(win, 1, 2, "%.*s", textw, title);
|
||||
|
||||
int sel = 0, top = 0, result = -1, done = 0;
|
||||
|
||||
while (!done) {
|
||||
// Mantiene la selezione dentro l'area visibile
|
||||
if (sel < top) top = sel;
|
||||
if (sel >= top + visible) top = sel - visible + 1;
|
||||
|
||||
for (int i = 0; i < visible; i++) {
|
||||
mvwhline(win, 3 + i, 1, ' ', width - 2);
|
||||
int idx = top + i;
|
||||
if (idx >= n) continue;
|
||||
if (selectable && idx == sel) wattron(win, A_REVERSE);
|
||||
mvwprintw(win, 3 + i, 2, "%.*s", textw, lines[idx]);
|
||||
if (selectable && idx == sel) wattroff(win, A_REVERSE);
|
||||
}
|
||||
if (n == 0)
|
||||
mvwprintw(win, 3, 2, "Nessun elemento.");
|
||||
|
||||
mvwhline(win, height - 3, 1, ' ', width - 2);
|
||||
if (footer)
|
||||
mvwprintw(win, height - 3, 2, "%.*s", textw, footer);
|
||||
|
||||
mvwhline(win, height - 2, 1, ' ', width - 2);
|
||||
mvwprintw(win, height - 2, 2, "%.*s", textw,
|
||||
selectable ? "SU/GIU scorri INVIO scegli q annulla"
|
||||
: "SU/GIU scorri q chiudi");
|
||||
wrefresh(win);
|
||||
|
||||
switch (wgetch(win)) {
|
||||
case KEY_UP: case 'k': if (sel > 0) sel--; break;
|
||||
case KEY_DOWN: case 'j': if (sel < n - 1) sel++; break;
|
||||
case KEY_PPAGE:
|
||||
sel -= visible;
|
||||
if (sel < 0) sel = 0;
|
||||
break;
|
||||
case KEY_NPAGE:
|
||||
sel += visible;
|
||||
if (sel > n - 1) sel = n - 1;
|
||||
if (sel < 0) sel = 0;
|
||||
break;
|
||||
case 10: case 32:
|
||||
if (selectable && n > 0) result = sel;
|
||||
done = 1;
|
||||
break;
|
||||
case 'q':
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
werase(win);
|
||||
destroy_win(win);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Finestre
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
void example_window() {
|
||||
WINDOW *win;
|
||||
@@ -41,8 +182,8 @@ int runMainMenu(){
|
||||
"Segna vendita",
|
||||
"Vedi vendite",
|
||||
"Vedi prodotti",
|
||||
"Gestisci prodotti",
|
||||
"Opzioni",
|
||||
"Aggiungi prodotti",
|
||||
"About",
|
||||
"Exit"
|
||||
};
|
||||
int n_mainMenu = sizeof(mainMenu) / sizeof(char *);
|
||||
@@ -67,6 +208,63 @@ int runMainMenu(){
|
||||
return choice;
|
||||
}
|
||||
|
||||
void runAboutWindow(){
|
||||
WINDOW *aboutWin;
|
||||
aboutWin = create_newwin(LINES/2-2, COLS/2, LINES/2, 0);
|
||||
|
||||
wwrap_print(aboutWin, 1, 1, "Questo e' un software fatto per Roma2LUG, il codice verra' pubblicato sul gitea del lug in futuro. La parte logica e' fatta con Claude Code, non avevo voglia di scrivere quei pezzi e volevo effettivamente rendere utile il codice che avevo scritto per gestire ncurses. Oh well, spero vi possa essere comunque utile. Buon lavoro / Divertimento!");
|
||||
statusf("Uovo di pasqua ()");
|
||||
wrefresh(aboutWin);
|
||||
getch();
|
||||
werase(aboutWin);
|
||||
destroy_win(aboutWin);
|
||||
}
|
||||
|
||||
void runAddProductWindow() {
|
||||
WINDOW *addWin = create_newwin(LINES / 2, COLS / 2, 0, COLS / 2);
|
||||
|
||||
char name[33] = "";
|
||||
char priceStr[13] = "";
|
||||
char *opts[] = {"Salva", "Annulla"};
|
||||
|
||||
mvwprintw(addWin, 1, 2, "Aggiungi un nuovo prodotto");
|
||||
|
||||
mvwprintw(addWin, 3, 2, "Nome:");
|
||||
uiTextInput(addWin, 4, 2, name, 32);
|
||||
|
||||
mvwprintw(addWin, 6, 2, "Prezzo (es. 19.90):");
|
||||
uiTextInput(addWin, 7, 2, priceStr, 12);
|
||||
|
||||
int choice = uiSelectMenu(addWin, 9, 4, opts, 2);
|
||||
|
||||
if (choice == 1) {
|
||||
for (char *p = priceStr; *p; p++)
|
||||
if (*p == ',') *p = '.';
|
||||
|
||||
char *end;
|
||||
double price = strtod(priceStr, &end);
|
||||
|
||||
if (name[0] == '\0') {
|
||||
warningf("Il nome del prodotto non puo' essere vuoto.");
|
||||
} else if (priceStr[0] == '\0' || *end != '\0' || price < 0) {
|
||||
warningf("Il prezzo '%s' non e' valido.", priceStr);
|
||||
} else {
|
||||
int res = addProduct(productPricesFileLocation, name, price);
|
||||
if (res == 0)
|
||||
statusf("Prodotto '%s' aggiunto a %.2f", name, price);
|
||||
else if (res == 1)
|
||||
warningf("Il prodotto '%s' esiste gia'.", name);
|
||||
else
|
||||
warningf("Errore nella scrittura del file '%s'.", productPricesFileLocation);
|
||||
}
|
||||
} else {
|
||||
statusf("Operazione annullata");
|
||||
}
|
||||
|
||||
werase(addWin);
|
||||
destroy_win(addWin);
|
||||
}
|
||||
|
||||
void runOptionsMenu(){
|
||||
WINDOW *optionsWin;
|
||||
optionsWin = create_newwin(LINES/2, COLS/2, 0, COLS/2);
|
||||
@@ -87,3 +285,157 @@ void runOptionsMenu(){
|
||||
werase(optionsWin);
|
||||
destroy_win(optionsWin);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Segna vendita */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void runSellWindow() {
|
||||
json_object *products = getProducts(productPricesFileLocation);
|
||||
if (!products) {
|
||||
warningf("Impossibile leggere il file '%s'.", productPricesFileLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
if (json_object_array_length(products) == 0) {
|
||||
warningf("Non ci sono prodotti. Aggiungine uno da 'Gestisci prodotti'.");
|
||||
json_object_put(products);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Scelta del prodotto
|
||||
int n;
|
||||
char **lines = buildProductLines(products, &n);
|
||||
if (!lines) {
|
||||
warningf("Memoria insufficiente.");
|
||||
json_object_put(products);
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = listWindow("Segna vendita - scegli il prodotto", lines, n, NULL, 1);
|
||||
freeLines(lines, n);
|
||||
|
||||
if (idx < 0) {
|
||||
statusf("Operazione annullata");
|
||||
json_object_put(products);
|
||||
return;
|
||||
}
|
||||
|
||||
json_object *item = json_object_array_get_idx(products, idx);
|
||||
const char *name = jsonStr(item, "name");
|
||||
double price = jsonDouble(item, "price");
|
||||
|
||||
// 2. Quantita' venduta
|
||||
WINDOW *win = create_newwin(9, COLS / 2, 0, COLS / 2);
|
||||
keypad(win, TRUE);
|
||||
|
||||
char qtyStr[8] = "1";
|
||||
char *opts[] = {"Salva", "Annulla"};
|
||||
|
||||
mvwprintw(win, 1, 2, "Segna vendita");
|
||||
mvwprintw(win, 3, 2, "Prodotto: %.30s", name);
|
||||
mvwprintw(win, 4, 2, "Quantita' venduta:");
|
||||
uiTextInput(win, 5, 2, qtyStr, 6);
|
||||
|
||||
int choice = uiSelectMenu(win, 6, 4, opts, 2);
|
||||
|
||||
if (choice == 1) {
|
||||
char *end;
|
||||
long qty = strtol(qtyStr, &end, 10);
|
||||
|
||||
if (qtyStr[0] == '\0' || *end != '\0' || qty <= 0 || qty > 1000000) {
|
||||
warningf("La quantita' '%s' non e' valida.", qtyStr);
|
||||
} else if (addSold(countFileLocation, name, (int)qty) == 0) {
|
||||
statusf("Registrata vendita: %ld x '%s' = %.2f", qty, name, qty * price);
|
||||
} else {
|
||||
warningf("Errore nella scrittura del file '%s'.", countFileLocation);
|
||||
}
|
||||
} else {
|
||||
statusf("Operazione annullata");
|
||||
}
|
||||
|
||||
werase(win);
|
||||
destroy_win(win);
|
||||
json_object_put(products);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Vedi vendite */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void runViewSalesWindow() {
|
||||
json_object *sales = getProducts(countFileLocation);
|
||||
if (!sales) {
|
||||
warningf("Impossibile leggere il file '%s'.", countFileLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = (int)json_object_array_length(sales);
|
||||
char **lines = malloc((count > 0 ? count : 1) * sizeof *lines);
|
||||
if (!lines) {
|
||||
warningf("Memoria insufficiente.");
|
||||
json_object_put(sales);
|
||||
return;
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
json_object *item = json_object_array_get_idx(sales, i);
|
||||
const char *name = jsonStr(item, "name");
|
||||
int sold = jsonInt(item, "sold");
|
||||
double price = getPrice(productPricesFileLocation, name);
|
||||
|
||||
lines[i] = malloc(LINE_LEN);
|
||||
if (!lines[i])
|
||||
break;
|
||||
|
||||
if (price >= 0)
|
||||
snprintf(lines[i], LINE_LEN, "%-18.18s %4d %9.2f", name, sold, sold * price);
|
||||
else
|
||||
snprintf(lines[i], LINE_LEN, "%-18.18s %4d %9s", name, sold, "n/d");
|
||||
n++;
|
||||
}
|
||||
|
||||
char footer[64];
|
||||
double total = getTotalRevenue(productPricesFileLocation, countFileLocation);
|
||||
if (total >= 0)
|
||||
snprintf(footer, sizeof footer, "Entrate totali: %.2f", total);
|
||||
else
|
||||
snprintf(footer, sizeof footer, "Entrate totali: n/d");
|
||||
|
||||
listWindow("Vendite (prodotto / copie / incasso)", lines, n, footer, 0);
|
||||
|
||||
freeLines(lines, n);
|
||||
json_object_put(sales);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Vedi prodotti */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void runViewProductsWindow() {
|
||||
json_object *products = getProducts(productPricesFileLocation);
|
||||
if (!products) {
|
||||
warningf("Impossibile leggere il file '%s'.", productPricesFileLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
int n;
|
||||
char **lines = buildProductLines(products, &n);
|
||||
if (!lines) {
|
||||
warningf("Memoria insufficiente.");
|
||||
json_object_put(products);
|
||||
return;
|
||||
}
|
||||
|
||||
char footer[64];
|
||||
snprintf(footer, sizeof footer, "Prodotti in listino: %d", n);
|
||||
|
||||
listWindow("Prodotti (nome / prezzo)", lines, n, footer, 0);
|
||||
|
||||
freeLines(lines, n);
|
||||
json_object_put(products);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user