diff options
| author | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-08-10 19:03:46 +0200 |
|---|---|---|
| committer | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-08-10 19:03:46 +0200 |
| commit | 572caa74ed824fefa02eb81adc7639a783f243c7 (patch) | |
| tree | 9938efe957a708a3642c19a03fdacaaa2b0618d5 /src | |
| parent | 3c83a2d06cf33429ca0e654a415fc95581df46e1 (diff) | |
working on invoice form
Diffstat (limited to 'src')
| -rw-r--r-- | src/administration.cpp | 106 | ||||
| -rw-r--r-- | src/strops.cpp | 17 | ||||
| -rw-r--r-- | src/ui/helpers.cpp | 14 | ||||
| -rw-r--r-- | src/ui/ui_contacts.cpp | 31 | ||||
| -rw-r--r-- | src/ui/ui_invoices.cpp | 81 | ||||
| -rw-r--r-- | src/ui/ui_projects.cpp | 2 |
6 files changed, 217 insertions, 34 deletions
diff --git a/src/administration.cpp b/src/administration.cpp index 869855e..a34ece8 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -199,6 +199,71 @@ static void administration_create_default_cost_centers() ADD_COSTCENTER("costcenter.other_specialized", "OTHR"); } +static void administration_create_debug_data() +{ + #define ADD_CONSUMER(_name, _addr1, _addr2, _cc)\ + {contact _c = administration_create_empty_contact();\ + strops_copy(_c.name, _name, sizeof(_c.name));\ + strops_copy(_c.address.address1, _addr1, sizeof(_c.address.address1));\ + strops_copy(_c.address.address2, _addr2, sizeof(_c.address.address2));\ + strops_copy(_c.address.country_code, _cc, sizeof(_c.address.country_code));\ + _c.type = contact_type::CONTACT_CONSUMER;\ + administration_create_contact(_c);}; + + #define ADD_BUSINESS(_name, _addr1, _addr2, _cc, _tc, _bc)\ + {contact _c = administration_create_empty_contact();\ + strops_copy(_c.name, _name, sizeof(_c.name));\ + strops_copy(_c.address.address1, _addr1, sizeof(_c.address.address1));\ + strops_copy(_c.address.address2, _addr2, sizeof(_c.address.address2));\ + strops_copy(_c.address.country_code, _cc, sizeof(_c.address.country_code));\ + strops_copy(_c.taxid, _tc, sizeof(_c.taxid));\ + strops_copy(_c.businessid, _bc, sizeof(_c.businessid));\ + _c.type = contact_type::CONTACT_BUSINESS;\ + administration_create_contact(_c);}; + + #define ADD_PROJECT(_name)\ + {project _c = administration_create_empty_project();\ + strops_copy(_c.description, _name, sizeof(_c.description));\ + administration_create_project(_c);}; + + ADD_CONSUMER("Emma Müller", "Hauptstraße 12", "10115 Berlin", "DE"); + ADD_CONSUMER("Luca Rossi", "Via Roma 45", "00184 Roma", "IT"); + ADD_CONSUMER("Sofia Garcia", "Calle Mayor 7", "28013 Madrid", "ES"); + ADD_CONSUMER("Jean Dupont", "10 Rue de la Paix", "75002 Paris", "FR"); + ADD_CONSUMER("Anna Nowak", "ul. Kwiatowa 3", "00-001 Warszawa", "PL"); + ADD_CONSUMER("Mikkel Jensen", "Østergade 8", "8000 Aarhus", "DK"); + ADD_CONSUMER("Maria Svensson", "Kungsgatan 15", "111 22 Stockholm", "SE"); + ADD_CONSUMER("Péter Kovács", "Fő utca 25", "1051 Budapest", "HU"); + ADD_CONSUMER("Lucas Silva", "Rua Augusta 100", "1250-001 Lisboa", "PT"); + ADD_CONSUMER("Isabelle Lefevre", "5 Place Stanislas", "54000 Nancy", "FR"); + + ADD_BUSINESS("Schmidt & Co GmbH", "Friedrichstraße 45", "10117 Berlin", "DE", "DE123456789", "HRB123456"); + ADD_BUSINESS("Bianchi Srl", "Corso Venezia 12", "20121 Milano", "IT", "IT987654321", "MI1234567"); + ADD_BUSINESS("Fernández y Asociados", "Gran Vía 20", "28013 Madrid", "ES", "ES456789123", "CIFB123456"); + ADD_BUSINESS("Martin & Partners", "12 Avenue Victor Hugo", "75016 Paris", "FR", "FR321654987", "SIRET123456"); + ADD_BUSINESS("Zielińska Consulting", "ul. Marszałkowska 10", "00-590 Warszawa", "PL", "PL789123456", "REGON123456"); + ADD_BUSINESS("Sørensen ApS", "Strøget 3", "1460 København", "DK", "DK654321789", "CVR12345678"); + ADD_BUSINESS("Johansson AB", "Drottninggatan 22", "111 51 Stockholm", "SE", "SE987654321", "OrgNr1234567"); + ADD_BUSINESS("Nagy Kft.", "Andrássy út 60", "1062 Budapest", "HU", "HU123987654", "Cégjegyzékszám123"); + ADD_BUSINESS("Santos Lda.", "Avenida da Liberdade 50", "1250-142 Lisboa", "PT", "PT321789654", "NIPC123456789"); + ADD_BUSINESS("Dupuis SARL", "8 Rue Saint-Denis", "75001 Paris", "FR", "FR456123789", "SIREN123456"); + ADD_BUSINESS("Müller & Söhne GmbH", "Leipziger Platz 8", "10117 Berlin", "DE", "DE654987321", "HRB987654"); + ADD_BUSINESS("Romano Srl", "Via Garibaldi 14", "16124 Genova", "IT", "IT321654987", "GE1239876"); + ADD_BUSINESS("López Asociados", "Plaza del Pilar 6", "50003 Zaragoza", "ES", "ES789321654", "CIFC654321"); + ADD_BUSINESS("Laurent & Fils", "15 Boulevard Haussmann", "75009 Paris", "FR", "FR987321654", "SIRET654987"); + ADD_BUSINESS("Kowalczyk Sp. z o.o.", "ul. Piotrkowska 55", "90-001 Łódź", "PL", "PL123456789", "REGON654321"); + ADD_BUSINESS("Nielsen ApS", "Nørregade 12", "1165 København", "DK", "DK789456123", "CVR87654321"); + ADD_BUSINESS("Lindberg AB", "Vasagatan 18", "111 20 Stockholm", "SE", "SE456789123", "OrgNr7654321"); + ADD_BUSINESS("Szabó Kft.", "Kossuth Lajos tér 1", "1055 Budapest", "HU", "HU987123654", "Cégjegyzékszám654321"); + ADD_BUSINESS("Costa Lda.", "Rua do Ouro 24", "1100-063 Lisboa", "PT", "PT654123987", "NIPC987654321"); + ADD_BUSINESS("Moreau SARL", "3 Place de la République", "75011 Paris", "FR", "FR321456987", "SIREN789123"); + + ADD_PROJECT("eCommerce"); + ADD_PROJECT("Retail store #1"); + ADD_PROJECT("Retail store #2"); + ADD_PROJECT("Kayak rental"); +} + void administration_create() { g_administration.next_id = 1; @@ -220,6 +285,7 @@ void administration_create() administration_create_default_tax_brackets(); administration_create_default_cost_centers(); + administration_create_debug_data(); } void administration_destroy() @@ -232,6 +298,8 @@ void administration_destroy() bool administration_create_contact(contact data) { + if (!administration_is_contact_valid(data)) return false; + contact* new_contact = (contact*)malloc(sizeof(contact)); memcpy((void*)new_contact, (void*)&data, sizeof(contact)); list_append(&g_administration.contacts, new_contact); @@ -250,6 +318,8 @@ bool administration_can_contact_be_deleted(contact data) bool administration_update_contact(contact data) { + if (!administration_is_contact_valid(data)) return false; + list_iterator_start(&g_administration.contacts); while (list_iterator_hasnext(&g_administration.contacts)) { contact* c = (contact *)list_iterator_next(&g_administration.contacts); @@ -309,6 +379,25 @@ u32 administration_get_contacts(u32 page_index, u32 page_size, contact* buffer) return write_cursor; } +int administration_get_contact_recommendations(contact* buffer, int buf_size, char* name) +{ + int write_cursor = 0; + if (name[0] == '\0') return 0; + + list_iterator_start(&g_administration.contacts); + while (list_iterator_hasnext(&g_administration.contacts)) { + contact c = *(contact *)list_iterator_next(&g_administration.contacts); + + if (strops_stristr(c.name, name)) { + buffer[write_cursor++] = c; + if (write_cursor >= buf_size) break; + } + } + list_iterator_stop(&g_administration.contacts); + + return write_cursor; +} + char* administration_get_file_path() { return g_administration.path; @@ -348,6 +437,11 @@ void administration_cancel_project(project data) administration_update_project(data); } +bool administration_is_project_valid(project data) +{ + return strlen(data.description) > 0; +} + char* administration_project_get_status_string(project data) { switch(data.state) @@ -362,6 +456,8 @@ char* administration_project_get_status_string(project data) bool administration_create_project(project data) { + if (!administration_is_project_valid(data)) return false; + data.state = project_state::PROJECT_RUNNING; data.start_date = time(NULL); data.end_date = 0; @@ -376,6 +472,8 @@ bool administration_create_project(project data) bool administration_update_project(project data) { + if (!administration_is_project_valid(data)) return false; + list_iterator_start(&g_administration.projects); while (list_iterator_hasnext(&g_administration.projects)) { project* c = (project *)list_iterator_next(&g_administration.projects); @@ -572,12 +670,20 @@ 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 +} + invoice administration_create_empty_invoice() { invoice result; memset(&result, 0, sizeof(invoice)); snprintf(result.id, sizeof(result.id), "I/%d", administration_create_id()); snprintf(result.sequential_number, sizeof(result.id), "INV%010d", administration_create_sequence_number()); + result.issued_at = time(NULL); + result.delivered_at = time(NULL); + result.expires_at = time(NULL) + administration_get_default_invoice_expire_duration(); return result; } diff --git a/src/strops.cpp b/src/strops.cpp index 42042cb..116eec4 100644 --- a/src/strops.cpp +++ b/src/strops.cpp @@ -1,4 +1,5 @@ #include <string.h> +#include <ctype.h> #include "strops.hpp" @@ -15,4 +16,20 @@ size_t strops_copy(char *dst, const char *src, size_t size) dst[srclen] = '\0'; return (srclen); +} + +char* strops_stristr(char* haystack, char* needle) +{ + do { + const char* h = haystack; + const char* n = needle; + while (tolower((unsigned char) *h) == tolower((unsigned char ) *n) && *n) { + h++; + n++; + } + if (*n == 0) { + return (char *) haystack; + } + } while (*haystack++); + return 0; }
\ No newline at end of file diff --git a/src/ui/helpers.cpp b/src/ui/helpers.cpp index a239f4c..4c6647e 100644 --- a/src/ui/helpers.cpp +++ b/src/ui/helpers.cpp @@ -65,20 +65,24 @@ void ui_helper_draw_required_tag() ImGui::PopStyleColor(); } -void ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, char* buffer, size_t buf_size, +int ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, char* buffer, size_t buf_size, char* suggestions[], int suggestion_count) { + int result = -1; static bool is_open = false; ImGui::InputTextWithHint(label, hint, buffer, buf_size); + if (buffer[0] == '\0' && is_open) is_open = false; + if (suggestion_count == 0 && is_open) is_open = false; + bool is_active = ImGui::IsItemActive(); - if (is_active && buffer[0] != '\0') + if (is_active && buffer[0] != '\0' && suggestion_count > 0) { is_open = true; } if (is_open) { - ImGui::BeginChild("autocomplete_popup", ImVec2(0, 100), true); + ImGui::BeginChild("autocomplete_popup", ImVec2(0, 10.0f + suggestion_count*23.0f), true); { ImVec2 win_pos = ImGui::GetWindowPos(); ImVec2 win_size = ImGui::GetWindowSize(); @@ -97,7 +101,7 @@ void ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, ch // Copy selected suggestion to buffer strops_copy(buffer, suggestions[i], buf_size); buffer[buf_size - 1] = '\0'; - + result = i; is_open = false; } } @@ -105,4 +109,6 @@ void ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, ch ImGui::EndChild(); } } + return result; } + diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp index c381db5..d57776e 100644 --- a/src/ui/ui_contacts.cpp +++ b/src/ui/ui_contacts.cpp @@ -18,9 +18,8 @@ void ui_setup_contacts() memset(&selected_for_removal, 0, sizeof(contact)); } -void draw_contact_form(contact* buffer, bool viewing_only = false) +void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0) { - bool with_autocomplete = false; const char* selected_country = NULL; ImGui::Spacing(); @@ -37,12 +36,26 @@ void draw_contact_form(contact* buffer, bool viewing_only = false) // 2. Full name ImGui::SetNextItemWidth(widthAvailable*0.5f); if (with_autocomplete) { - contact autocomplete_list[5]; - int autocomplete_count = 5; - char* autocomplete_strings[5] = { "1", "2", "3", "4", "5" }; + contact autocomplete_list[5]; + int autocomplete_count = administration_get_contact_recommendations(autocomplete_list, 5, buffer->name); + char* autocomplete_strings[5]; + + for (int i = 0; i < autocomplete_count; i++) + { + autocomplete_strings[i] = autocomplete_list[i].name; + } - ui_helper_TextInputWithAutocomplete(localize("contact.form.fullname"), localize("contact.form.fullname"), + int autocomplete_index = ui_helper_TextInputWithAutocomplete(localize("contact.form.fullname"), localize("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name), (char**)autocomplete_strings, autocomplete_count); + + if (on_autocomplete) { + *on_autocomplete = autocomplete_index != -1; + } + + if (autocomplete_index != -1) + { + memcpy(buffer, &autocomplete_list[autocomplete_index], sizeof(contact)); + } } else ImGui::InputTextWithHint(localize("contact.form.fullname"), localize("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name)); ImGui::SameLine();ui_helper_draw_required_tag(); @@ -131,6 +144,12 @@ void draw_contact_form(contact* buffer, bool viewing_only = false) if (viewing_only) ImGui::EndDisabled(); } +void draw_contact_form(contact* buffer, bool viewing_only = false) +{ + draw_contact_form_ex(buffer, viewing_only, false, 0); +} + + static void draw_contact_list() { const u32 items_per_page = 50; diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp index fe4f5a8..8ee099c 100644 --- a/src/ui/ui_invoices.cpp +++ b/src/ui/ui_invoices.cpp @@ -1,4 +1,7 @@ #include <stdio.h> +#include <time.h> + +#include "ImGuiDatePicker/ImGuiDatePicker.hpp" #include "strops.hpp" #include "ui.hpp" @@ -9,7 +12,7 @@ static view_state current_view_state = view_state::LIST; static invoice active_invoice; -extern void draw_contact_form(contact* buffer, bool viewing_only = false); +void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0); void ui_setup_invoices() { @@ -17,23 +20,16 @@ void ui_setup_invoices() active_invoice = administration_create_empty_invoice(); } -bool draw_invoice_form(invoice* buffer, bool back_button_enabled = true, bool viewing_only = false) +void draw_invoice_form(invoice* buffer, bool viewing_only = false) { - if (back_button_enabled) - { - if (ImGui::Button(localize("form.back"))) { - current_view_state = view_state::LIST; - return false; - } - } - ImGui::Spacing(); - float widthAvailable = ImGui::GetContentRegionAvail().x; + //float widthAvailable = ImGui::GetContentRegionAvail().x; + ImGui::BeginDisabled(); // 1. Identifier - //ImGui::BeginDisabled(); //ImGui::SetNextItemWidth(widthAvailable*0.2f); //ImGui::InputText(localize("contact.form.identifier"), buffer->id, IM_ARRAYSIZE(buffer->id)); - + if (!viewing_only) ImGui::EndDisabled(); + // 2. Sequential number ImGui::Text("Invoice number: %s", buffer->sequential_number); @@ -42,20 +38,54 @@ bool draw_invoice_form(invoice* buffer, bool back_button_enabled = true, bool vi ImGui::Separator(); + // 4. Customer information ImGui::Text("Customer information"); - draw_contact_form(&buffer->customer); - strops_copy(buffer->customer_id, buffer->customer.id, sizeof(buffer->customer_id)); + 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)); + } ImGui::Separator(); + // 5. Invoice issued at + ImGui::BeginDisabled(); + tm issued_at_date = *gmtime(&buffer->issued_at); + if (ImGui::DatePicker("##issuedAt", issued_at_date)) + { + buffer->issued_at = mktime(&issued_at_date); + } + ImGui::SameLine(); + ImGui::Text("Invoice issued at"); + ImGui::EndDisabled(); + + // 6. Invoice expires at + ImGui::BeginDisabled(); + tm expires_at_date = *gmtime(&buffer->expires_at); + if (ImGui::DatePicker("##expiresAt", expires_at_date)) + { + buffer->expires_at = mktime(&expires_at_date); + } + ImGui::SameLine(); + ImGui::Text("Invoice expires at"); + ImGui::EndDisabled(); + + // 7. Product/service delivered at + tm delivered_at_date = *gmtime(&buffer->delivered_at); + if (ImGui::DatePicker("##deliveredAt", delivered_at_date)) + { + buffer->delivered_at = mktime(&delivered_at_date); + } + ImGui::SameLine(); + ImGui::Text("Product/service delivered at"); + //ImGui::SetNextItemWidth(widthAvailable*0.5f); //ImGui::InputTextWithHint("Invoice number", "Invoice number", buffer->sequential_number, IM_ARRAYSIZE(buffer->sequential_number)); //ImGui::SameLine();ui_helper_draw_required_tag(); - //if (!viewing_only) ImGui::EndDisabled(); - - return false; + if (viewing_only) ImGui::EndDisabled(); } void draw_invoices_list() @@ -75,12 +105,17 @@ void ui_draw_invoices() switch(current_view_state) { case view_state::LIST: draw_invoices_list(); break; - case view_state::CREATE: - if (draw_invoice_form(&active_invoice)) - { - //administration_create_invoice(active_invoice); + case view_state::CREATE: + + if (ImGui::Button(localize("form.back"))) { current_view_state = view_state::LIST; - } + } + draw_invoice_form(&active_invoice); + + //if () { + //administration_create_invoice(active_invoice); + //current_view_state = view_state::LIST; + //} break; case view_state::EDIT: break; case view_state::VIEW: break; diff --git a/src/ui/ui_projects.cpp b/src/ui/ui_projects.cpp index 5cb412f..3c550a2 100644 --- a/src/ui/ui_projects.cpp +++ b/src/ui/ui_projects.cpp @@ -45,7 +45,7 @@ static void draw_project_form() if (viewing_only) ImGui::EndDisabled(); if (!viewing_only) { - bool can_save = strlen(active_project.description) > 0; + bool can_save = administration_is_project_valid(active_project); if (!can_save) ImGui::BeginDisabled(); // Save button |
