summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-08-16 20:24:28 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-08-16 20:24:28 +0200
commit543aa7d53136037f07302a5653bba90751ac1552 (patch)
tree0911a4fbba487fbb28acb6a5934af0a95c024c64
parent50848b2dd97093dd407ed7199118bca011f1aa4c (diff)
more refactors
-rw-r--r--include/administration.hpp57
-rw-r--r--include/ui.hpp3
-rw-r--r--src/administration.cpp468
-rw-r--r--src/ui/helpers.cpp2
-rw-r--r--src/ui/ui_contacts.cpp9
-rw-r--r--src/ui/ui_invoices.cpp94
-rw-r--r--src/ui/ui_main.cpp11
-rw-r--r--src/ui/ui_settings.cpp4
8 files changed, 382 insertions, 266 deletions
diff --git a/include/administration.hpp b/include/administration.hpp
index 219ece0..3415b3a 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -20,18 +20,20 @@
#define MAX_LEN_BUSINESSID 32
#define MAX_LEN_TAX_SECTION 16
+#define MAX_BILLING_ITEMS 50
+
typedef struct
{
- char id[MAX_LEN_ID];
- char country_code[MAX_LEN_COUNTRY_CODE];
- float rate; // 0-100
- char description[MAX_LEN_SHORT_DESC];
+ char id[MAX_LEN_ID]; // T/[id]
+ char country_code[MAX_LEN_COUNTRY_CODE]; // 2 letter country code
+ float rate; // 0-100%
+ char description[MAX_LEN_SHORT_DESC]; // Currently only used for global tax brackets
} country_tax_bracket;
typedef struct
{
- char id[MAX_LEN_ID];
- char code[MAX_LEN_CODE];
+ char id[MAX_LEN_ID]; // C/[id]
+ char code[MAX_LEN_CODE]; // Internal 4 letter code
char description[MAX_LEN_LONG_DESC];
} cost_center;
@@ -39,7 +41,7 @@ typedef struct
{
char address1[MAX_LEN_ADDRESS];
char address2[MAX_LEN_ADDRESS];
- char country_code[MAX_LEN_COUNTRY_CODE]; // 2 letter country code.
+ char country_code[MAX_LEN_COUNTRY_CODE]; // 2 letter country code
} address;
typedef enum
@@ -50,12 +52,12 @@ typedef enum
typedef struct
{
- char id[MAX_LEN_ID];
+ char id[MAX_LEN_ID]; // C/[id]
char name[MAX_LEN_LONG_DESC];
address address;
contact_type type;
- char taxid[MAX_LEN_TAXID];
- char businessid[MAX_LEN_TAXID];
+ char taxid[MAX_LEN_TAXID]; // Required for business contact
+ char businessid[MAX_LEN_TAXID]; // Required for business contact
char email[MAX_LEN_EMAIL];
char phone_number[MAX_LEN_PHONE];
char bank_account[MAX_LEN_BANK];
@@ -70,11 +72,11 @@ typedef enum
typedef struct
{
- char id[MAX_LEN_ID];
+ char id[MAX_LEN_ID]; // P/[id]
char description[MAX_LEN_LONG_DESC];
project_state state;
time_t start_date;
- time_t end_date;
+ time_t end_date; // Set when project is cancelled
} project;
typedef enum
@@ -91,8 +93,8 @@ typedef enum
typedef struct
{
- char id[MAX_LEN_ID];
- char invoice_id[MAX_LEN_ID];
+ char id[MAX_LEN_ID]; // B/[id]
+ char invoice_id[MAX_LEN_ID]; // I/[id]
float amount;
bool amount_is_percentage;
char description[MAX_LEN_LONG_DESC];
@@ -100,21 +102,21 @@ typedef struct
float discount;
bool discount_is_percentage;
float net;
- char currency[MAX_LEN_CURRENCY];
- char tax_bracket_id[MAX_LEN_ID];
+ char currency[MAX_LEN_CURRENCY]; // 3 letter code
+ char tax_bracket_id[MAX_LEN_ID]; // T/[id]
float tax;
float total;
- // todo
+ // TODO uninplemented
char tax_section[MAX_LEN_TAX_SECTION];
} billing_item;
typedef struct
{
- char id[MAX_LEN_ID];
+ char id[MAX_LEN_ID]; // I/[id]
char sequential_number[MAX_LEN_SEQ_NUM]; // INV0000000000 - INV9999999999
- char customer_id[MAX_LEN_ID];
- char supplier_id[MAX_LEN_ID];
+ char customer_id[MAX_LEN_ID]; // C/[id]
+ char supplier_id[MAX_LEN_ID]; // C/[id]
contact addressee;
time_t issued_at;
time_t expires_at;
@@ -129,14 +131,14 @@ typedef struct
float tax;
float net;
- char currency[MAX_LEN_CURRENCY];
+ char currency[MAX_LEN_CURRENCY]; // 3 letter code
bool is_triangulation; // True of addressee != customer
invoice_status status;
- bool is_intra_community; // TODO
- time_t payment_on_account_date; // TODO
- char tax_representative[MAX_LEN_LONG_DESC]; // TODO
- char corrected_sequential_number[MAX_LEN_ID]; // TODO
+ bool is_intra_community; // TODO uninplemented
+ time_t payment_on_account_date; // TODO uninplemented
+ char tax_representative[MAX_LEN_LONG_DESC]; // TODO uninplemented
+ char corrected_sequential_number[MAX_LEN_ID]; // TODO uninplemented
// Used for forms, not stored on disk. Filled when retrieved.
contact supplier;
@@ -179,7 +181,9 @@ bool administration_contact_remove(contact data);
bool administration_contact_is_valid(contact data);
bool administration_contact_can_be_deleted(contact data);
+bool administration_contact_equals(contact c1, contact c2);
+bool administration_contact_get_by_id(contact* buffer, char* id);
int administration_contact_get_autocompletions(contact* buffer, int buf_size, char* name);
u32 administration_contact_get_partial_list(u32 page_index, u32 page_size, contact* buffer);
@@ -224,6 +228,7 @@ u32 administration_invoice_count();
invoice administration_invoice_create_empty();
bool administration_invoice_add(invoice* invoice);
bool administration_invoice_update(invoice* invoice);
+bool administration_invoice_remove(invoice* invoice);
void administration_invoice_set_currency(invoice* invoice, char* currency);
invoice administration_invoice_create_copy(invoice* invoice);
@@ -234,7 +239,7 @@ u32 administration_invoice_get_partial_list(u32 page_index, u32 page_size, i
// Billing item functions.
// =======================
-u32 administration_billing_items_count(invoice* invoice);
+u32 administration_billing_item_count(invoice* invoice);
billing_item administration_billing_item_create_empty();
bool administration_billing_item_add_to_invoice(invoice* invoice, billing_item item);
bool administration_billing_item_update_in_invoice(invoice* invoice, billing_item item);
diff --git a/include/ui.hpp b/include/ui.hpp
index 67e0529..a594095 100644
--- a/include/ui.hpp
+++ b/include/ui.hpp
@@ -21,5 +21,8 @@ void ui_setup_contacts();
void ui_setup_projects();
void ui_setup_settings();
+void ui_destroy_invoices();
+void ui_destroy_settings();
+
// Custom imgui widgets.
int TextInputWithAutocomplete(const char* label, const char* hint, char* buffer, size_t buf_size, char** suggestions, int suggestion_count); \ No newline at end of file
diff --git a/src/administration.cpp b/src/administration.cpp
index 4d8233e..ace1383 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -264,14 +264,29 @@ static void administration_create_debug_data()
ADD_PROJECT("Kayak rental");
// Company info.
+
+ snprintf(g_administration.company_info.id, sizeof(g_administration.company_info.id), "C/%d", administration_create_id());
strops_copy(g_administration.company_info.name, "Aldrik Ramaekers", sizeof(g_administration.company_info.name));
strops_copy(g_administration.company_info.address.address1, "Keerderstraat 81", sizeof(g_administration.company_info.address.address1));
strops_copy(g_administration.company_info.address.address2, "6226XW Maastricht", sizeof(g_administration.company_info.address.address2));
strops_copy(g_administration.company_info.address.country_code, "NL", sizeof(g_administration.company_info.address.country_code));
strops_copy(g_administration.company_info.taxid, "123", sizeof(g_administration.company_info.taxid));
strops_copy(g_administration.company_info.businessid, "123", sizeof(g_administration.company_info.businessid));
+ g_administration.next_id++;
+}
+
+static s32 administration_create_sequence_number()
+{
+ return g_administration.next_sequence_number;
}
+static time_t administration_get_default_invoice_expire_duration()
+{
+ return (30 * 24 * 60 * 60); // 30 days
+}
+
+// Setup functions.
+// =======================
void administration_create()
{
g_administration.next_id = 1;
@@ -284,23 +299,50 @@ void administration_create()
list_init(&g_administration.cost_centers);
strops_copy(g_administration.path, "", sizeof(g_administration.path));
- snprintf(g_administration.company_info.id, sizeof(g_administration.company_info.id), "C/%d", administration_create_id());
- g_administration.next_id++;
-
administration_create_default_tax_brackets();
administration_create_default_cost_centers();
administration_create_debug_data();
}
+static void administration_destroy_list(list_t *list)
+{
+ list_iterator_start(list);
+ while (list_iterator_hasnext(list)) {
+ void* c = (void *)list_iterator_next(list);
+ free(c);
+ }
+ list_iterator_stop(list);
+ list_destroy(list);
+}
+
void administration_destroy()
{
- list_destroy(&g_administration.invoices);
- list_destroy(&g_administration.contacts);
- list_destroy(&g_administration.projects);
- list_destroy(&g_administration.tax_brackets);
- list_destroy(&g_administration.cost_centers);
+ administration_destroy_list(&g_administration.invoices);
+ administration_destroy_list(&g_administration.contacts);
+ administration_destroy_list(&g_administration.projects);
+ administration_destroy_list(&g_administration.tax_brackets);
+ administration_destroy_list(&g_administration.cost_centers);
}
+// General functions.
+// =======================
+char* administration_file_path_get()
+{
+ return g_administration.path;
+}
+
+contact administration_company_info_get()
+{
+ return g_administration.company_info;
+}
+
+void administration_company_info_set(contact data)
+{
+ g_administration.company_info = data;
+}
+
+// Contact functions.
+// =======================
bool administration_contact_add(contact data)
{
if (!administration_contact_is_valid(data)) return false;
@@ -349,6 +391,7 @@ bool administration_contact_remove(contact data)
if (strcmp(c->id, data.id) == 0) {
list_iterator_stop(&g_administration.contacts);
list_delete(&g_administration.contacts, c);
+ free(c);
return true;
}
}
@@ -384,6 +427,24 @@ u32 administration_contact_get_partial_list(u32 page_index, u32 page_size, conta
return write_cursor;
}
+bool administration_contact_get_by_id(contact* buffer, char* id)
+{
+ bool result = false;
+ list_iterator_start(&g_administration.contacts);
+ while (list_iterator_hasnext(&g_administration.contacts)) {
+ contact c = *(contact *)list_iterator_next(&g_administration.contacts);
+
+ if (strcmp(c.id, id) == 0) {
+ list_iterator_stop(&g_administration.projects);
+ *buffer = c;
+ result = true;
+ }
+ }
+ list_iterator_stop(&g_administration.contacts);
+
+ return result;
+}
+
int administration_contact_get_autocompletions(contact* buffer, int buf_size, char* name)
{
int write_cursor = 0;
@@ -403,33 +464,39 @@ int administration_contact_get_autocompletions(contact* buffer, int buf_size, ch
return write_cursor;
}
-char* administration_file_path_get()
+bool administration_contact_is_valid(contact data)
{
- return g_administration.path;
+ if (data.type == contact_type::CONTACT_CONSUMER)
+ {
+ return strlen(data.name) > 0 && strlen(data.address.address1) > 0 && strlen(data.address.address2) > 0 && strlen(data.address.country_code) > 0;
+ }
+ else if (data.type == contact_type::CONTACT_BUSINESS)
+ {
+ return strlen(data.name) > 0 && strlen(data.address.address1) > 0 && strlen(data.address.address2) > 0 && strlen(data.address.country_code) > 0
+ && strlen(data.taxid) > 0 && strlen(data.businessid);
+ }
+
+ return false;
}
-u32 administration_project_count()
+contact administration_contact_create_empty()
{
- return list_size(&g_administration.projects);
+ contact result;
+ memset(&result, 0, sizeof(contact));
+ snprintf(result.id, sizeof(result.id), "C/%d", administration_create_id());
+ return result;
}
-u32 administration_billing_items_count(invoice* invoice)
+bool administration_contact_equals(contact c1, contact c2)
{
- return list_size(&invoice->billing_items);
+ return memcmp(&c1, &c2, sizeof(contact)) == 0;
}
-u32 administration_billing_item_get_all_for_invoice(invoice* invoice, billing_item* buffer)
+// Project functions.
+// =======================
+u32 administration_project_count()
{
- u32 write_cursor = 0;
-
- list_iterator_start(&invoice->billing_items);
- while (list_iterator_hasnext(&invoice->billing_items)) {
- billing_item c = *(billing_item *)list_iterator_next(&invoice->billing_items);
- buffer[write_cursor++] = c;
- }
- list_iterator_stop(&invoice->billing_items);
-
- return write_cursor;
+ return list_size(&g_administration.projects);
}
u32 administration_project_get_all(project* buffer)
@@ -480,23 +547,6 @@ bool administration_project_is_valid(project data)
return strlen(data.description) > 0;
}
-char* administration_invoice_get_status_string(invoice* invoice)
-{
- switch(invoice->status)
- {
- case invoice_status::INVOICE_CONCEPT: return "invoice.state.concept";
- case invoice_status::INVOICE_SENT: return "invoice.state.sent";
- case invoice_status::INVOICE_REMINDED: return "invoice.state.reminded";
- case invoice_status::INVOICE_PAID: return "invoice.state.paid";
- case invoice_status::INVOICE_EXPIRED: return "invoice.state.expired";
- case invoice_status::INVOICE_CANCELLED: return "invoice.state.cancelled";
- case invoice_status::INVOICE_REFUNDED: return "invoice.state.refunded";
- case invoice_status::INVOICE_CORRECTED: return "invoice.state.corrected";
- default: assert(0); break;
- }
- return "";
-}
-
char* administration_project_get_status_string(project data)
{
switch(data.state)
@@ -553,6 +603,7 @@ bool administration_project_remove(project data)
if (strcmp(c->id, data.id) == 0) {
list_iterator_stop(&g_administration.projects);
list_delete(&g_administration.projects, c);
+ free(c);
return true;
}
}
@@ -561,14 +612,33 @@ bool administration_project_remove(project data)
return false;
}
-contact administration_company_info_get()
+project administration_project_create_empty()
{
- return g_administration.company_info;
+ project result;
+ memset(&result, 0, sizeof(project));
+ snprintf(result.id, sizeof(result.id), "P/%d", administration_create_id());
+ return result;
}
-void administration_company_info_set(contact data)
+// Tax bracket functions.
+// =======================
+static bool administration_get_tax_bracket_by_id(country_tax_bracket* buffer, char* id)
{
- g_administration.company_info = data;
+ assert(buffer);
+
+ list_iterator_start(&g_administration.tax_brackets);
+ while (list_iterator_hasnext(&g_administration.tax_brackets)) {
+ country_tax_bracket c = *(country_tax_bracket *)list_iterator_next(&g_administration.tax_brackets);
+ if (strcmp(c.id, id) == 0)
+ {
+ *buffer = c;
+ list_iterator_stop(&g_administration.tax_brackets);
+ return true;
+ }
+ }
+ list_iterator_stop(&g_administration.tax_brackets);
+
+ return false;
}
u32 administration_tax_bracket_count()
@@ -634,6 +704,8 @@ bool administration_tax_bracket_update(country_tax_bracket data)
return false;
}
+// Cost center functions.
+// =======================
u32 administration_cost_center_count()
{
return list_size(&g_administration.cost_centers);
@@ -720,52 +792,8 @@ bool administration_cost_center_update(cost_center data)
return false;
}
-bool administration_contact_is_valid(contact data)
-{
- if (data.type == contact_type::CONTACT_CONSUMER)
- {
- return strlen(data.name) > 0 && strlen(data.address.address1) > 0 && strlen(data.address.address2) > 0 && strlen(data.address.country_code) > 0;
- }
- else if (data.type == contact_type::CONTACT_BUSINESS)
- {
- return strlen(data.name) > 0 && strlen(data.address.address1) > 0 && strlen(data.address.address2) > 0 && strlen(data.address.country_code) > 0
- && strlen(data.taxid) > 0 && strlen(data.businessid);
- }
-
- return false;
-}
-
-static s32 administration_create_sequence_number()
-{
- return g_administration.next_sequence_number;
-}
-
-static time_t administration_get_default_invoice_expire_duration()
-{
- return (30 * 24 * 60 * 60); // 30 days
-}
-
-billing_item administration_billing_item_create_empty()
-{
- billing_item item;
- memset(&item, 0, sizeof(billing_item));
- item.amount = 1;
- return item;
-}
-
-bool administration_billing_item_add_to_invoice(invoice* invoice, billing_item item)
-{
- billing_item* tb = (billing_item*)malloc(sizeof(billing_item));
- memcpy(tb, &item, sizeof(billing_item));
- snprintf(tb->id, sizeof(tb->id), "B/%d", administration_create_id());
- strops_copy(tb->invoice_id, invoice->id, sizeof(tb->invoice_id));
- list_append(&invoice->billing_items, tb);
- strops_copy(tb->currency, invoice->currency, MAX_LEN_CURRENCY); // Set billing item currency to invoice currency.
-
- g_administration.next_id++;
-
- return true;
-}
+// Invoice functions.
+// =======================
static char* administration_get_default_currency_for_country(char* country_code)
{
@@ -821,41 +849,6 @@ invoice administration_invoice_create_empty()
return result;
}
-contact administration_contact_create_empty()
-{
- contact result;
- memset(&result, 0, sizeof(contact));
- snprintf(result.id, sizeof(result.id), "C/%d", administration_create_id());
- return result;
-}
-
-project administration_project_create_empty()
-{
- project result;
- memset(&result, 0, sizeof(project));
- snprintf(result.id, sizeof(result.id), "P/%d", administration_create_id());
- return result;
-}
-
-static bool administration_get_tax_bracket_by_id(country_tax_bracket* buffer, char* id)
-{
- assert(buffer);
-
- list_iterator_start(&g_administration.tax_brackets);
- while (list_iterator_hasnext(&g_administration.tax_brackets)) {
- country_tax_bracket c = *(country_tax_bracket *)list_iterator_next(&g_administration.tax_brackets);
- if (strcmp(c.id, id) == 0)
- {
- *buffer = c;
- list_iterator_stop(&g_administration.tax_brackets);
- return true;
- }
- }
- list_iterator_stop(&g_administration.tax_brackets);
-
- return false;
-}
-
static void administration_recalculate_invoice_totals(invoice* invoice)
{
invoice->tax = 0.0f;
@@ -873,75 +866,6 @@ static void administration_recalculate_invoice_totals(invoice* invoice)
list_iterator_stop(&invoice->billing_items);
}
-static void administration_recalculate_billing_item_totals(billing_item* item)
-{
- if (item->amount_is_percentage)
- {
- item->net = item->net_per_item * (item->amount / 100.0f);
- }
- else
- {
- item->net = item->net_per_item * item->amount;
- }
-
- if (item->discount != 0)
- {
- if (item->discount_is_percentage)
- {
- item->net -= item->net * (item->discount / 100.0f);
- }
- else
- {
- item->net -= item->discount;
- }
- }
-
- country_tax_bracket bracket;
- if (administration_get_tax_bracket_by_id(&bracket, item->tax_bracket_id))
- {
- item->tax = item->net * (bracket.rate/100.0f);
- }
-
- item->total = item->net + item->tax;
-}
-
-bool administration_billing_item_remove_from_invoice(invoice* invoice, billing_item item)
-{
- list_iterator_start(&invoice->billing_items);
- while (list_iterator_hasnext(&invoice->billing_items)) {
- billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items);
-
- if (strcmp(c->id, item.id) == 0) {
- list_iterator_stop(&invoice->billing_items);
- list_delete(&invoice->billing_items, c);
- return true;
- }
- }
- list_iterator_stop(&invoice->billing_items);
-
- return false;
-}
-
-bool administration_billing_item_update_in_invoice(invoice* invoice, billing_item item)
-{
- list_iterator_start(&invoice->billing_items);
- while (list_iterator_hasnext(&invoice->billing_items)) {
- billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items);
-
- if (strcmp(c->id, item.id) == 0) {
- memcpy(c, &item, sizeof(billing_item));
- list_iterator_stop(&invoice->billing_items);
-
- administration_recalculate_billing_item_totals(c);
- administration_recalculate_invoice_totals(invoice);
- return true;
- }
- }
- list_iterator_stop(&invoice->billing_items);
-
- return false;
-}
-
void administration_invoice_set_currency(invoice* invoice, char* currency)
{
strops_copy(invoice->currency, currency, MAX_LEN_CURRENCY);
@@ -965,6 +889,25 @@ bool administration_invoice_is_valid(invoice* invoice)
return true;
}
+bool administration_invoice_remove(invoice* inv)
+{
+ list_iterator_start(&g_administration.invoices);
+ while (list_iterator_hasnext(&g_administration.invoices)) {
+ invoice* c = (invoice *)list_iterator_next(&g_administration.invoices);
+
+ if (strcmp(c->id, inv->id) == 0)
+ {
+ list_iterator_stop(&g_administration.invoices);
+ administration_destroy_list(&c->billing_items);
+ list_delete(&g_administration.invoices, c);
+ free(c);
+ return true;
+ }
+ }
+ list_iterator_stop(&g_administration.invoices);
+ return false;
+}
+
bool administration_invoice_update(invoice* inv)
{
if (!administration_invoice_is_valid(inv)) return false;
@@ -1084,4 +1027,137 @@ u32 administration_invoice_get_partial_list(u32 page_index, u32 page_size, invoi
list_iterator_stop(&g_administration.invoices);
return write_cursor;
+}
+
+char* administration_invoice_get_status_string(invoice* invoice)
+{
+ switch(invoice->status)
+ {
+ case invoice_status::INVOICE_CONCEPT: return "invoice.state.concept";
+ case invoice_status::INVOICE_SENT: return "invoice.state.sent";
+ case invoice_status::INVOICE_REMINDED: return "invoice.state.reminded";
+ case invoice_status::INVOICE_PAID: return "invoice.state.paid";
+ case invoice_status::INVOICE_EXPIRED: return "invoice.state.expired";
+ case invoice_status::INVOICE_CANCELLED: return "invoice.state.cancelled";
+ case invoice_status::INVOICE_REFUNDED: return "invoice.state.refunded";
+ case invoice_status::INVOICE_CORRECTED: return "invoice.state.corrected";
+ default: assert(0); break;
+ }
+ return "";
+}
+
+// Billing item functions.
+// =======================
+
+static void administration_recalculate_billing_item_totals(billing_item* item)
+{
+ if (item->amount_is_percentage)
+ {
+ item->net = item->net_per_item * (item->amount / 100.0f);
+ }
+ else
+ {
+ item->net = item->net_per_item * item->amount;
+ }
+
+ if (item->discount != 0)
+ {
+ if (item->discount_is_percentage)
+ {
+ item->net -= item->net * (item->discount / 100.0f);
+ }
+ else
+ {
+ item->net -= item->discount;
+ }
+ }
+
+ country_tax_bracket bracket;
+ if (administration_get_tax_bracket_by_id(&bracket, item->tax_bracket_id))
+ {
+ item->tax = item->net * (bracket.rate/100.0f);
+ }
+
+ item->total = item->net + item->tax;
+}
+
+u32 administration_billing_item_count(invoice* invoice)
+{
+ return list_size(&invoice->billing_items);
+}
+
+u32 administration_billing_item_get_all_for_invoice(invoice* invoice, billing_item* buffer)
+{
+ u32 write_cursor = 0;
+
+ list_iterator_start(&invoice->billing_items);
+ while (list_iterator_hasnext(&invoice->billing_items)) {
+ billing_item c = *(billing_item *)list_iterator_next(&invoice->billing_items);
+ buffer[write_cursor++] = c;
+ }
+ list_iterator_stop(&invoice->billing_items);
+
+ return write_cursor;
+}
+
+bool administration_billing_item_remove_from_invoice(invoice* invoice, billing_item item)
+{
+ list_iterator_start(&invoice->billing_items);
+ while (list_iterator_hasnext(&invoice->billing_items)) {
+ billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items);
+
+ if (strcmp(c->id, item.id) == 0) {
+ list_iterator_stop(&invoice->billing_items);
+ list_delete(&invoice->billing_items, c);
+ free(c);
+ return true;
+ }
+ }
+ list_iterator_stop(&invoice->billing_items);
+
+ return false;
+}
+
+bool administration_billing_item_update_in_invoice(invoice* invoice, billing_item item)
+{
+ list_iterator_start(&invoice->billing_items);
+ while (list_iterator_hasnext(&invoice->billing_items)) {
+ billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items);
+
+ if (strcmp(c->id, item.id) == 0) {
+ memcpy(c, &item, sizeof(billing_item));
+ list_iterator_stop(&invoice->billing_items);
+
+ administration_recalculate_billing_item_totals(c);
+ administration_recalculate_invoice_totals(invoice);
+ return true;
+ }
+ }
+ list_iterator_stop(&invoice->billing_items);
+
+ return false;
+}
+
+bool administration_billing_item_add_to_invoice(invoice* invoice, billing_item item)
+{
+ if (administration_billing_item_count(invoice) >= MAX_BILLING_ITEMS) return false;
+
+ billing_item* tb = (billing_item*)malloc(sizeof(billing_item));
+ memcpy(tb, &item, sizeof(billing_item));
+ snprintf(tb->id, sizeof(tb->id), "B/%d", administration_create_id());
+ strops_copy(tb->invoice_id, invoice->id, sizeof(tb->invoice_id));
+ list_append(&invoice->billing_items, tb);
+ strops_copy(tb->currency, invoice->currency, MAX_LEN_CURRENCY); // Set billing item currency to invoice currency.
+
+ g_administration.next_id++;
+
+ return true;
+}
+
+billing_item administration_billing_item_create_empty()
+{
+ billing_item item;
+ memset(&item, 0, sizeof(billing_item));
+ item.amount = 1;
+ return item;
} \ No newline at end of file
diff --git a/src/ui/helpers.cpp b/src/ui/helpers.cpp
index 7bde384..6ca1bd2 100644
--- a/src/ui/helpers.cpp
+++ b/src/ui/helpers.cpp
@@ -59,6 +59,7 @@ int TextInputWithAutocomplete(const char* label, const char* hint, char* buffer,
for (int i = 0; i < suggestion_count; ++i)
{
+ ImGui::PushID(i);
if (ImGui::Selectable(suggestions[i]))
{
// Copy selected suggestion to buffer
@@ -67,6 +68,7 @@ int TextInputWithAutocomplete(const char* label, const char* hint, char* buffer,
result = i;
is_open = false;
}
+ ImGui::PopID();
}
ImGui::EndChild();
diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp
index a55fd73..dbe6625 100644
--- a/src/ui/ui_contacts.cpp
+++ b/src/ui/ui_contacts.cpp
@@ -1,4 +1,5 @@
#include <stdio.h>
+#include <stdlib.h>
#include "strops.hpp"
#include "ui.hpp"
@@ -99,7 +100,8 @@ void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_
for (int i = 0; i < autocomplete_count; i++)
{
- autocomplete_strings[i] = autocomplete_list[i].name;
+ autocomplete_strings[i] = (char*)malloc(200);
+ snprintf(autocomplete_strings[i], 200, "%s (%s %s)", autocomplete_list[i].name, autocomplete_list[i].address.address1, autocomplete_list[i].address.address2);
}
int autocomplete_index = TextInputWithAutocomplete(localize("contact.form.fullname"), localize("contact.form.fullname"),
@@ -113,6 +115,11 @@ void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_
{
memcpy(buffer, &autocomplete_list[autocomplete_index], sizeof(contact));
}
+
+ for (int i = 0; i < autocomplete_count; i++)
+ {
+ free(autocomplete_strings[i]);
+ }
}
else ImGui::InputTextWithHint(localize("contact.form.fullname"), localize("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name));
ImGui::SameLine();ui_helper_draw_required_tag();
diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp
index c08dd76..320392a 100644
--- a/src/ui/ui_invoices.cpp
+++ b/src/ui/ui_invoices.cpp
@@ -11,20 +11,25 @@
#include "administration.hpp"
#include "locales.hpp"
-void ui_draw_address_form(address* buffer);
static view_state current_view_state = view_state::LIST;
static invoice active_invoice = {0};
+static invoice selected_for_removal = {0};
cost_center* cost_center_list_buffer = 0;
country_tax_bracket* tax_bracket_list_buffer = 0;
+project* project_list_buffer = 0;
+billing_item* invoice_items_buffer = 0;
+void ui_draw_address_form(address* buffer);
void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0);
void ui_destroy_invoices()
{
free(cost_center_list_buffer);
free(tax_bracket_list_buffer);
+ free(project_list_buffer);
+ free(invoice_items_buffer);
}
void ui_setup_invoices()
@@ -33,10 +38,16 @@ void ui_setup_invoices()
active_invoice = administration_invoice_create_empty();
u32 costcenter_count = administration_cost_center_count();
- cost_center_list_buffer = (cost_center*) malloc(sizeof(cost_center) * costcenter_count); // @leak
+ cost_center_list_buffer = (cost_center*) malloc(sizeof(cost_center) * costcenter_count);
u32 tax_bracket_count = administration_tax_bracket_count();
- tax_bracket_list_buffer = (country_tax_bracket*) malloc(sizeof(country_tax_bracket) * tax_bracket_count); // @leak
+ tax_bracket_list_buffer = (country_tax_bracket*) malloc(sizeof(country_tax_bracket) * tax_bracket_count);
+
+ u32 project_count = administration_project_count();
+ project_list_buffer = (project*) malloc(sizeof(project) * project_count);
+
+ u32 invoice_items_count = MAX_BILLING_ITEMS;
+ invoice_items_buffer = (billing_item*)malloc(sizeof(billing_item) * invoice_items_count);
}
void draw_tax_bracket_selector(char* tax_bracket_id)
@@ -185,10 +196,8 @@ void draw_costcenter_selector(char* costcenter_id)
void draw_project_selector(char* project_id)
{
project* selected_project = NULL;
-
- u32 project_count = administration_project_count();
- project* buffer = (project*) malloc(sizeof(project) * project_count);
- project_count = administration_project_get_all(buffer);
+ project* buffer = project_list_buffer;
+ u32 project_count = administration_project_get_all(buffer);
// Select project by given id.
if (strlen(project_id) > 0)
@@ -219,15 +228,12 @@ void draw_project_selector(char* project_id)
if (selected_project_index != -1) {
strops_copy(project_id, buffer[selected_project_index].id, MAX_LEN_ID);
}
-
- free(buffer);
}
static void draw_invoice_items_form(invoice* invoice)
{
- u32 invoice_items = administration_billing_items_count(invoice);
- billing_item* buffer = (billing_item*)malloc(sizeof(billing_item) * invoice_items);
- administration_billing_item_get_all_for_invoice(invoice, buffer);
+ billing_item* buffer = invoice_items_buffer;
+ u32 invoice_items = administration_billing_item_get_all_for_invoice(invoice, buffer);
if (ImGui::BeginTable("TableBillingItems", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
@@ -343,8 +349,6 @@ static void draw_invoice_items_form(invoice* invoice)
ImGui::EndTable();
}
-
- free(buffer);
}
static void draw_invoice_form(invoice* buffer, bool viewing_only = false)
@@ -396,12 +400,20 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false)
bool on_autocomplete;
draw_contact_form_ex(&buffer->customer, false, true, &on_autocomplete);
- // TODO: check if customer info is equal to contact stored in customer id, in case we select from dropdown and edit data after,
- // this should be handled as a new contact and customer_id should be set to "" so we create a new contact.
-
if (on_autocomplete) {
strops_copy(buffer->customer_id, buffer->customer.id, sizeof(buffer->customer_id));
}
+
+ // Check if contact info is equal to contact stored in customer id, in case we select from dropdown and edit data after,
+ // this should be handled as a new contact and customer_id should be set to "" so we create a new contact.
+ contact lookup_buffer;
+ if (administration_contact_get_by_id(&lookup_buffer, buffer->customer_id))
+ {
+ if (!administration_contact_equals(lookup_buffer, buffer->customer))
+ {
+ buffer->customer_id[0] = '\0';
+ }
+ }
// 8. (optional) shipping address.
ImGui::Checkbox("Shipping information differs from billing information (triangulation)", &buffer->is_triangulation);
@@ -424,11 +436,14 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false)
ImGui::Spacing();
// 11. New billing item button.
+ bool max_items_reached = administration_billing_item_count(buffer) >= MAX_BILLING_ITEMS;
+ if (max_items_reached) ImGui::BeginDisabled();
if (ImGui::Button(localize("+ Billing item")))
{
billing_item item = administration_billing_item_create_empty();
administration_billing_item_add_to_invoice(buffer, item);
}
+ if (max_items_reached) ImGui::EndDisabled();
// 12. Dropdown for invoice currency.
ImGui::SameLine();
@@ -530,34 +545,31 @@ static void ui_draw_invoices_list()
active_invoice = administration_invoice_create_copy(&c); // We create a copy because of billing item list pointers.
current_view_state = view_state::EDIT;
}
+
+ ImGui::SameLine();
+ snprintf(btn_name, sizeof(btn_name), "%s##%d", localize("form.delete"), i);
+ if (ImGui::Button(btn_name)) {
+ selected_for_removal = c;
+ ImGui::OpenPopup("ConfirmDeletePopup");
+ }
}
-
- // ImGui::SameLine();
- // if (administration_contact_can_be_deleted(c))
- // {
- // snprintf(btn_name, sizeof(btn_name), "%s##%d", localize("form.delete"), i);
- // if (ImGui::Button(btn_name)) {
- // selected_for_removal = c;
- // ImGui::OpenPopup("ConfirmDeletePopup");
- // }
- // }
}
// Confirmation popup before contact is deleted definitively.
- // if (ImGui::BeginPopupModal("ConfirmDeletePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) {
- // ImGui::Text(localize("form.confirmDelete"));
- // ImGui::Separator();
-
- // if (ImGui::Button(localize("form.yes"), ImVec2(120, 0))) {
- // administration_contact_remove(selected_for_removal);
- // ImGui::CloseCurrentPopup();
- // }
- // ImGui::SameLine();
- // if (ImGui::Button(localize("form.no"), ImVec2(120, 0))) {
- // ImGui::CloseCurrentPopup();
- // }
- // ImGui::EndPopup();
- // }
+ if (ImGui::BeginPopupModal("ConfirmDeletePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) {
+ ImGui::Text(localize("form.confirmDelete"));
+ ImGui::Separator();
+
+ if (ImGui::Button(localize("form.yes"), ImVec2(120, 0))) {
+ administration_invoice_remove(&selected_for_removal);
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::SameLine();
+ if (ImGui::Button(localize("form.no"), ImVec2(120, 0))) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
ImGui::EndTable();
}
diff --git a/src/ui/ui_main.cpp b/src/ui/ui_main.cpp
index d8fd5d4..19ab6b4 100644
--- a/src/ui/ui_main.cpp
+++ b/src/ui/ui_main.cpp
@@ -37,8 +37,19 @@ void (*setupcalls[dashboard_view_state::END])(void) = {
ui_setup_settings,
};
+void (*destroycalls[dashboard_view_state::END])(void) = {
+ ui_destroy_invoices,
+ 0,
+ 0,
+ 0,
+ 0,
+ ui_destroy_settings,
+ 0,
+};
+
static void set_dashboard_state(dashboard_view_state state)
{
+ if (dashboard_state != dashboard_view_state::END && destroycalls[dashboard_state]) destroycalls[dashboard_state]();
dashboard_state = state;
if (setupcalls[dashboard_state]) setupcalls[dashboard_state]();
}
diff --git a/src/ui/ui_settings.cpp b/src/ui/ui_settings.cpp
index 69c4f54..3f9ab6d 100644
--- a/src/ui/ui_settings.cpp
+++ b/src/ui/ui_settings.cpp
@@ -11,11 +11,11 @@ extern void draw_contact_form(contact* buffer, bool viewing_only = false);
static contact company_info;
-country_tax_bracket* tax_brackets;
u32 tax_bracket_count;
+country_tax_bracket* tax_brackets = 0;
-cost_center* cost_centers;
u32 cost_center_count;
+cost_center* cost_centers = 0;
void ui_destroy_settings()
{