diff options
| author | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-08-17 17:35:06 +0200 |
|---|---|---|
| committer | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-08-17 17:35:06 +0200 |
| commit | 8ea59863c5d13e68e080cf7612047ea4c655292c (patch) | |
| tree | c9e71b722854cb10e34de193e77f5e3220d9d4e1 /src | |
| parent | b676299f983b6fc00458953d90efe3f784b86d62 (diff) | |
debug data for invoices and expenses
Diffstat (limited to 'src')
| -rw-r--r-- | src/administration.cpp | 259 | ||||
| -rw-r--r-- | src/ui/ui_expenses.cpp | 23 | ||||
| -rw-r--r-- | src/ui/ui_invoices.cpp | 23 |
3 files changed, 200 insertions, 105 deletions
diff --git a/src/administration.cpp b/src/administration.cpp index 45a1125..68714d3 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -32,6 +32,76 @@ static int compare_tax_countries(const void *a, const void *b) return strcmp(objA->country_code, objB->country_code); } +static invoice_status administation_get_random_invoice_status() +{ + return (invoice_status)(rand() % invoice_status::INVOICE_END); +} + +static void administation_get_random_project(char* project_id) +{ + int size = list_size(&g_administration.projects); + if (size > 0) { + int index = rand() % size; + project *val = (project *) list_get_at(&g_administration.projects, index); + strops_copy(project_id, val->id, MAX_LEN_ID); + } +} + +static void administation_get_random_cost_center(char* cost_center_id) +{ + int size = list_size(&g_administration.cost_centers); + if (size > 0) { + int index = rand() % size; + cost_center *val = (cost_center *) list_get_at(&g_administration.cost_centers, index); + strops_copy(cost_center_id, val->id, MAX_LEN_ID); + } +} + +static contact* administation_get_random_contact() +{ + int size = list_size(&g_administration.contacts); + if (size > 0) { + int index = rand() % size; + contact *val = (contact *) list_get_at(&g_administration.contacts, index); + return val; + } + return 0; +} + +static time_t administration_get_default_invoice_expire_duration() +{ + return (30 * 24 * 60 * 60); // 30 days +} + +static void administration_recalculate_billing_item_totals(billing_item* item); + +static void administation_get_random_billing_items(invoice* inv) +{ + int amount = 1 + rand() % 20; + + for (int i = 0; i < amount; i++) + { + billing_item item = administration_billing_item_create_empty(); + item.amount = 1 + (float)(rand() % 5); + item.amount_is_percentage = (float)(rand() % 2); + item.description[MAX_LEN_LONG_DESC]; + item.net_per_item = (float)(rand() % 100); + item.discount = (float)(rand() % 30); + item.discount_is_percentage = (float)(rand() % 2); + + administration_recalculate_billing_item_totals(&item); + + if (item.net < item.discount) item.discount = 0.0f; + + country_tax_bracket buffer[20]; + u32 bracket_count = administration_tax_bracket_get_by_country(buffer, inv->supplier.address.country_code); + country_tax_bracket rand_bracket = buffer[rand() % bracket_count]; + strops_copy(item.tax_bracket_id, rand_bracket.id, MAX_LEN_ID); + + administration_billing_item_add_to_invoice(inv, item); + } +} + static void administration_create_default_tax_brackets() { // General brackets shared between countries. @@ -201,6 +271,8 @@ static void administration_create_default_cost_centers() static void administration_create_debug_data() { + srand((unsigned) time(NULL)); + #define ADD_CONSUMER(_name, _addr1, _addr2, _cc)\ {contact _c = administration_contact_create_empty();\ strops_copy(_c.name, _name, sizeof(_c.name));\ @@ -264,7 +336,6 @@ 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)); @@ -273,6 +344,27 @@ static void administration_create_debug_data() 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++; + + // Invoices + #define ADD_INVOICE(_outgoing)\ + {\ + invoice inv = administration_invoice_create_empty();\ + inv.supplier = administration_company_info_get();\ + inv.customer = *administation_get_random_contact();\ + administation_get_random_project(inv.project_id);\ + administation_get_random_cost_center(inv.cost_center_id);\ + inv.is_outgoing = _outgoing;\ + inv.status = administation_get_random_invoice_status();\ + inv.issued_at = time(NULL) - (86400 * (rand() % 720));\ + inv.delivered_at = inv.issued_at;\ + inv.expires_at = inv.issued_at + administration_get_default_invoice_expire_duration();\ + administation_get_random_billing_items(&inv);\ + administration_invoice_add(&inv);\ + } + + // Create about 2 years of random data. + for (int i = 0; i < 410; i++) ADD_INVOICE(1); + for (int i = 0; i < 700; i++) ADD_INVOICE(0); } static s32 administration_create_sequence_number() @@ -280,11 +372,6 @@ 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() @@ -301,6 +388,7 @@ void administration_create() administration_create_default_tax_brackets(); administration_create_default_cost_centers(); + administration_create_debug_data(); } @@ -429,6 +517,14 @@ u32 administration_contact_get_partial_list(u32 page_index, u32 page_size, conta bool administration_contact_get_by_id(contact* buffer, char* id) { + // Include company info in contact lookup because this might be + // used in forms. + if (strcmp(id, g_administration.company_info.id) == 0) + { + *buffer = g_administration.company_info; + return true; + } + bool result = false; list_iterator_start(&g_administration.contacts); while (list_iterator_hasnext(&g_administration.contacts)) { @@ -900,6 +996,10 @@ bool administration_invoice_remove(invoice* inv) list_iterator_stop(&g_administration.invoices); administration_destroy_list(&c->billing_items); list_delete(&g_administration.invoices, c); + + if (inv->is_outgoing) g_administration.invoice_count--; + else g_administration.expense_count--; + free(c); return true; } @@ -908,21 +1008,27 @@ bool administration_invoice_remove(invoice* inv) return false; } -bool administration_invoice_update(invoice* inv) +static void administration_invoice_set_refs(invoice* inv) { - if (!administration_invoice_is_valid(inv)) return false; + // This function makes sure that contact info referenced in the IDs actually + // matches the contact info stored in administation, if not we make new contacts. - // Invoice is valid but customer id is unset means we need to register a new contact. - if (strcmp(inv->customer_id, "") == 0) + // Check if supplier info is equal to contact stored in supplier id, in case we autocomplete and edit data after, + // this should be handled as a new contact. + contact lookup_buffer; + if (administration_contact_get_by_id(&lookup_buffer, inv->supplier.id)) { - contact new_contact = administration_contact_create_empty(); - strops_copy(inv->customer_id, new_contact.id, sizeof(new_contact.id)); - strops_copy(inv->customer.id, new_contact.id, sizeof(new_contact.id)); - - memcpy(&new_contact, &inv->customer, sizeof(contact)); - administration_contact_add(new_contact); - - inv->customer = new_contact; + if (!administration_contact_equals(lookup_buffer, inv->supplier)) + { + // Zero id so new contact is created for supplier. + inv->supplier_id[0] = '\0'; + inv->supplier.id[0] = '\0'; + } + else + { + // Store supplier id in invoice, (only id is stored to disk, supplier field is filled on load). + strops_copy(inv->supplier_id, inv->supplier.id, sizeof(inv->supplier_id)); + } } // Supplier is valid but supplier id is unset means we need to register a new contact. @@ -938,11 +1044,48 @@ bool administration_invoice_update(invoice* inv) inv->supplier = new_contact; } + // Check if customer info is equal to contact stored in customer id, in case we autocomplete and edit data after, + // this should be handled as a new contact. + if (administration_contact_get_by_id(&lookup_buffer, inv->customer.id)) + { + if (!administration_contact_equals(lookup_buffer, inv->customer)) + { + // Zero id so new contact is created for supplier. + inv->customer_id[0] = '\0'; + inv->customer.id[0] = '\0'; + } + else + { + // Store customer id in invoice, (only id is stored to disk, supplier field is filled on load). + strops_copy(inv->customer_id, inv->customer.id, sizeof(inv->customer_id)); + } + } + + // Invoice is valid but customer id is unset means we need to register a new contact. + if (strcmp(inv->customer_id, "") == 0) + { + contact new_contact = administration_contact_create_empty(); + strops_copy(inv->customer_id, new_contact.id, sizeof(new_contact.id)); + strops_copy(inv->customer.id, new_contact.id, sizeof(new_contact.id)); + + memcpy(&new_contact, &inv->customer, sizeof(contact)); + administration_contact_add(new_contact); + + inv->customer = new_contact; + } + // Addressee is same as customer. if (!inv->is_triangulation) { memcpy(&inv->addressee, &inv->customer, sizeof(contact)); } +} + +bool administration_invoice_update(invoice* inv) +{ + if (!administration_invoice_is_valid(inv)) return false; + + administration_invoice_set_refs(inv); list_iterator_start(&g_administration.invoices); while (list_iterator_hasnext(&g_administration.invoices)) { @@ -959,6 +1102,27 @@ bool administration_invoice_update(invoice* inv) return false; } +bool administration_invoice_add(invoice* inv) +{ + if (!administration_invoice_is_valid(inv)) return false; + + administration_invoice_set_refs(inv); + + invoice copy = administration_invoice_create_copy(inv); // Create copy to make copy of billing item list. + invoice* new_inv = (invoice*)malloc(sizeof(invoice)); + memcpy(new_inv, ©, sizeof(invoice)); + + list_append(&g_administration.invoices, new_inv); + + g_administration.next_id++; + g_administration.next_sequence_number++; + + if (inv->is_outgoing) g_administration.invoice_count++; + else g_administration.expense_count++; + + return true; +} + invoice administration_invoice_create_copy(invoice* inv) { invoice new_inv = administration_invoice_create_empty(); @@ -980,57 +1144,19 @@ invoice administration_invoice_create_copy(invoice* inv) return new_inv; } -bool administration_invoice_add(invoice* inv) +u32 administration_invoice_count() { - if (!administration_invoice_is_valid(inv)) return false; - - // Invoice is valid but customer id is unset means we need to register a new contact. - if (strcmp(inv->customer_id, "") == 0) - { - contact new_contact = administration_contact_create_empty(); - strops_copy(inv->customer_id, new_contact.id, sizeof(new_contact.id)); - strops_copy(inv->customer.id, new_contact.id, sizeof(new_contact.id)); - - memcpy(&new_contact, &inv->customer, sizeof(contact)); - administration_contact_add(new_contact); - - inv->customer = new_contact; - } - - // Supplier is valid but supplier id is unset means we need to register a new contact. - if (strcmp(inv->supplier_id, "") == 0) - { - contact new_contact = administration_contact_create_empty(); - strops_copy(inv->supplier_id, new_contact.id, sizeof(new_contact.id)); - strops_copy(inv->supplier.id, new_contact.id, sizeof(new_contact.id)); - - memcpy(&new_contact, &inv->supplier, sizeof(contact)); - administration_contact_add(new_contact); - - inv->supplier = new_contact; - } - - // Addressee is same as customer. - if (!inv->is_triangulation) - { - memcpy(&inv->addressee, &inv->customer, sizeof(contact)); - } - - invoice copy = administration_invoice_create_copy(inv); // Create copy to make copy of billing item list. - invoice* new_inv = (invoice*)malloc(sizeof(invoice)); - memcpy(new_inv, ©, sizeof(invoice)); - - list_append(&g_administration.invoices, new_inv); - - g_administration.next_id++; - g_administration.next_sequence_number++; + return list_size(&g_administration.invoices); +} - return true; +u32 administation_invoice_get_incomming_count() +{ + return g_administration.expense_count; } -u32 administration_invoice_count() +u32 administation_invoice_get_outgoing_count() { - return list_size(&g_administration.invoices); + return g_administration.invoice_count; } static u32 administration_invoice_get_partial_list(u32 page_index, u32 page_size, invoice* buffer, bool want_outgoing) @@ -1184,9 +1310,12 @@ bool administration_billing_item_add_to_invoice(invoice* invoice, billing_item i 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. + + administration_recalculate_billing_item_totals(tb); + administration_recalculate_invoice_totals(invoice); + list_append(&invoice->billing_items, tb); g_administration.next_id++; return true; diff --git a/src/ui/ui_expenses.cpp b/src/ui/ui_expenses.cpp index 94b0b36..6d1b36c 100644 --- a/src/ui/ui_expenses.cpp +++ b/src/ui/ui_expenses.cpp @@ -289,24 +289,7 @@ static void draw_expense_form(invoice* buffer, bool viewing_only = false) // 7. Supplier information ImGui::Text(localize("invoice.form.supplier")); - bool on_autocomplete; - draw_contact_form_ex(&buffer->supplier, false, true, &on_autocomplete); - - if (on_autocomplete) { - strops_copy(buffer->supplier_id, buffer->supplier.id, sizeof(buffer->supplier_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 supplier_id should be set to "" so we create a new contact. - contact lookup_buffer; - if (administration_contact_get_by_id(&lookup_buffer, buffer->supplier_id)) - { - if (!administration_contact_equals(lookup_buffer, buffer->supplier)) - { - buffer->supplier_id[0] = '\0'; - buffer->supplier.id[0] = '\0'; - } - } + draw_contact_form_ex(&buffer->supplier, false, true, 0); // 8. (optional) shipping address. ImGui::Checkbox(localize("invoice.form.triangulation"), &buffer->is_triangulation); @@ -360,8 +343,9 @@ static void ui_draw_expenses_list() invoice invoice_list[items_per_page]; u32 invoice_count = administration_invoice_get_partial_list_incomming(current_page, items_per_page, invoice_list); + u32 total_invoice_count = administation_invoice_get_incomming_count(); - s32 max_page = (invoice_count + items_per_page - 1) / items_per_page; + s32 max_page = (total_invoice_count + items_per_page - 1) / items_per_page; if (max_page == 0) max_page = 1; // Table header controls: create button and pagination. @@ -372,7 +356,6 @@ static void ui_draw_expenses_list() active_invoice.customer = administration_company_info_get(); active_invoice.is_outgoing = 0; active_invoice.status = invoice_status::INVOICE_RECEIVED; - strops_copy(active_invoice.customer_id, active_invoice.customer.id, sizeof(active_invoice.customer_id)); } if (current_page >= max_page-1) current_page = max_page-1; diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp index 2ae0aae..d6bb157 100644 --- a/src/ui/ui_invoices.cpp +++ b/src/ui/ui_invoices.cpp @@ -309,24 +309,7 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false) // 7. Customer information ImGui::Text(localize("invoice.form.billinginformation")); - bool on_autocomplete; - draw_contact_form_ex(&buffer->customer, false, true, &on_autocomplete); - - 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'; - buffer->customer.id[0] = '\0'; - } - } + draw_contact_form_ex(&buffer->customer, false, true, 0); // 8. (optional) shipping address. ImGui::Checkbox(localize("invoice.form.triangulation"), &buffer->is_triangulation); @@ -373,8 +356,9 @@ static void ui_draw_invoices_list() invoice invoice_list[items_per_page]; u32 invoice_count = administration_invoice_get_partial_list_outgoing(current_page, items_per_page, invoice_list); + u32 total_invoice_count = administation_invoice_get_outgoing_count(); - s32 max_page = (invoice_count + items_per_page - 1) / items_per_page; + s32 max_page = (total_invoice_count + items_per_page - 1) / items_per_page; if (max_page == 0) max_page = 1; // Table header controls: create button and pagination. @@ -385,7 +369,6 @@ static void ui_draw_invoices_list() active_invoice.supplier = administration_company_info_get(); active_invoice.is_outgoing = 1; active_invoice.status = invoice_status::INVOICE_CONCEPT; - strops_copy(active_invoice.supplier_id, active_invoice.supplier.id, sizeof(active_invoice.supplier_id)); } if (current_page >= max_page-1) current_page = max_page-1; |
