diff options
| -rw-r--r-- | docs/README.rst | 3 | ||||
| -rw-r--r-- | include/administration.hpp | 26 | ||||
| -rw-r--r-- | src/administration.cpp | 79 | ||||
| -rw-r--r-- | src/ui/ui_contacts.cpp | 3 | ||||
| -rw-r--r-- | src/ui/ui_invoices.cpp | 101 |
5 files changed, 160 insertions, 52 deletions
diff --git a/docs/README.rst b/docs/README.rst index a9d4c18..469fab4 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -20,13 +20,14 @@ What OpenBooks **can** do: - OpenBooks can export/email invoices in PDF, UBL and Peppol format. - OpenBooks can send and receive invoices over the Peppol network (using a locally run `Holodeck <https://holodeck-b2b.org/>`_ instance). - OpenBooks can create tax reports and audit files (see supported countries list). +- OpenBooks can create Intra-Community transactions declaration (ICP) reports. What OpenBooks **can't** do: - Intrastat reporting - Sustainability Reporting (CSRD) - PoS Fiscalization -- Submit tax and audit reports +- Automatically submit tax, audit or ICP reports 2. Supported countries ---------------------- diff --git a/include/administration.hpp b/include/administration.hpp index fd55978..af449bb 100644 --- a/include/administration.hpp +++ b/include/administration.hpp @@ -62,6 +62,9 @@ typedef struct typedef enum { + INVOICE_CONCEPT, + INVOICE_SENT, + INVOICE_REMINDED, INVOICE_PAID, INVOICE_EXPIRED, INVOICE_CANCELLED, @@ -84,14 +87,11 @@ typedef struct float net; char currency[CURRENCY_LENGTH]; char tax_bracket_id[16]; + float tax; + float total; // todo char tax_section[16]; - float tax; - float total; - bool is_intra_community; - bool is_triangulation; - char internal_code[64]; } billing_item; typedef struct @@ -100,26 +100,27 @@ typedef struct char sequential_number[16]; // INV0000000000 - INV9999999999 char customer_id[16]; char supplier_id[16]; - address shipping_address; + contact addressee; time_t issued_at; time_t expires_at; time_t delivered_at; char document[255]; // path to copy of document for incomming invoice. char project_id[16]; char cost_center_id[16]; - list_t billing_items; float total; float tax; float net; - invoice_status status; char currency[CURRENCY_LENGTH]; - + bool is_triangulation; + + bool is_intra_community; // TODO + invoice_status status; // TODO time_t payment_on_account_date; // TODO char tax_representative[64]; // TODO char corrected_sequential_number[16]; // TODO - // Not stored. + // Used for forms, not stored on disk. Filled when retrieved. contact supplier; contact customer; } invoice; @@ -184,9 +185,12 @@ bool administration_update_cost_center(cost_center data); invoice administration_create_empty_invoice(); void administration_invoice_set_currency(invoice* invoice, char* currency); +bool administration_is_invoice_valid(invoice* invoice); +bool administration_add_invoice(invoice* invoice); billing_item administration_create_empty_billing_item(); u32 administration_get_all_billing_items_for_invoice(invoice* invoice, billing_item* buffer); u32 administration_get_billing_items_count(invoice* invoice); bool administration_add_billing_item_to_invoice(invoice* invoice, billing_item item); -bool administration_update_billing_item_of_invoice(invoice* invoice, billing_item item);
\ No newline at end of file +bool administration_update_billing_item_of_invoice(invoice* invoice, billing_item item); +bool administration_remove_billing_item_from_invoice(invoice* invoice, billing_item item);
\ No newline at end of file diff --git a/src/administration.cpp b/src/administration.cpp index 2e16775..6ed7d63 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -268,6 +268,8 @@ static void administration_create_debug_data() 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)); } void administration_create() @@ -275,6 +277,7 @@ void administration_create() g_administration.next_id = 1; g_administration.next_sequence_number = 1; + list_init(&g_administration.invoices); list_init(&g_administration.contacts); list_init(&g_administration.projects); list_init(&g_administration.tax_brackets); @@ -291,6 +294,7 @@ void administration_create() void administration_destroy() { + list_destroy(&g_administration.invoices); list_destroy(&g_administration.contacts); list_destroy(&g_administration.projects); list_destroy(&g_administration.tax_brackets); @@ -835,6 +839,23 @@ static bool administration_get_tax_bracket_by_id(country_tax_bracket* buffer, ch return false; } +static void administration_recalculate_invoice_totals(invoice* invoice) +{ + invoice->tax = 0.0f; + invoice->total = 0.0f; + invoice->net = 0.0f; + + list_iterator_start(&invoice->billing_items); + while (list_iterator_hasnext(&invoice->billing_items)) { + billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items); + + invoice->tax += c->tax; + invoice->total += c->total; + invoice->net += c->net; + } + list_iterator_stop(&invoice->billing_items); +} + static void administration_recalculate_billing_item_totals(billing_item* item) { if (item->amount_is_percentage) @@ -867,6 +888,23 @@ static void administration_recalculate_billing_item_totals(billing_item* item) item->total = item->net + item->tax; } +bool administration_remove_billing_item_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_update_billing_item_of_invoice(invoice* invoice, billing_item item) { list_iterator_start(&invoice->billing_items); @@ -874,9 +912,11 @@ bool administration_update_billing_item_of_invoice(invoice* invoice, billing_ite billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items); if (strcmp(c->id, item.id) == 0) { - memcpy(c, &item, sizeof(billing_item)); - administration_recalculate_billing_item_totals(c); + 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; } } @@ -896,4 +936,39 @@ void administration_invoice_set_currency(invoice* invoice, char* currency) strops_copy(c->currency, currency, CURRENCY_LENGTH); } list_iterator_stop(&invoice->billing_items); +} + +bool administration_is_invoice_valid(invoice* invoice) +{ + if (list_size(&invoice->billing_items) == 0) return false; + if (invoice->is_triangulation && !administration_is_contact_valid(invoice->addressee)) return false; + if (!administration_is_contact_valid(invoice->customer)) return false; + if (!administration_is_contact_valid(invoice->supplier)) return false; + + return true; +} + +bool administration_add_invoice(invoice* inv) +{ + if (!administration_is_invoice_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, "")) + { + contact new_contact = administration_create_empty_contact(); + strops_copy(inv->customer.id, new_contact.id, sizeof(new_contact.id)); + + memcpy(&new_contact, &inv->customer, sizeof(contact)); + administration_create_contact(new_contact); + } + + invoice* new_inv = (invoice*)malloc(sizeof(invoice)); + memcpy((void*)new_inv, (void*)inv, sizeof(invoice)); + + list_append(&g_administration.invoices, new_inv); + + g_administration.next_id++; + g_administration.next_sequence_number++; + + return true; }
\ No newline at end of file diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp index 7d03ba6..eca6ee3 100644 --- a/src/ui/ui_contacts.cpp +++ b/src/ui/ui_contacts.cpp @@ -77,6 +77,8 @@ void ui_setup_contacts() void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0) { + ImGui::PushID(buffer); + ImGui::Spacing(); float widthAvailable = ImGui::GetContentRegionAvail().x; @@ -152,6 +154,7 @@ void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_ ImGui::InputTextWithHint(localize("contact.form.bankaccount"), localize("contact.form.bankaccount"), buffer->bank_account, IM_ARRAYSIZE(buffer->bank_account)); if (viewing_only) ImGui::EndDisabled(); + ImGui::PopID(); } void draw_contact_form(contact* buffer, bool viewing_only = false) diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp index 036d85b..8c0943c 100644 --- a/src/ui/ui_invoices.cpp +++ b/src/ui/ui_invoices.cpp @@ -225,14 +225,15 @@ static void draw_invoice_items_form(invoice* invoice) billing_item* buffer = (billing_item*)malloc(sizeof(billing_item) * invoice_items); administration_get_all_billing_items_for_invoice(invoice, buffer); - if (ImGui::BeginTable("TableBillingItems", 8, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (ImGui::BeginTable("TableBillingItems", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("##actions", ImGuiTableColumnFlags_WidthFixed, 20); ImGui::TableSetupColumn("Amount", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Description"); ImGui::TableSetupColumn("Price", ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn("Discount", ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn("Net", ImGuiTableColumnFlags_WidthFixed, 100); - ImGui::TableSetupColumn("Tax %", ImGuiTableColumnFlags_WidthFixed, 170); + ImGui::TableSetupColumn("Tax %", ImGuiTableColumnFlags_WidthFixed, 120); ImGui::TableSetupColumn("Tax", ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn("Total", ImGuiTableColumnFlags_WidthFixed, 100); @@ -246,8 +247,14 @@ static void draw_invoice_items_form(invoice* invoice) ImGui::TableNextRow(); ImGui::PushID(i); + + ImGui::TableSetColumnIndex(0); + if (ImGui::Button("X")) + { + administration_remove_billing_item_from_invoice(invoice, item); + } - ImGui::TableSetColumnIndex(0); + ImGui::TableSetColumnIndex(1); ImGui::InputFloat("##amount", &item.amount, 0.0f, 0.0f, "%.0f"); ImGui::SameLine(); @@ -268,13 +275,17 @@ static void draw_invoice_items_form(invoice* invoice) } } - ImGui::TableSetColumnIndex(1); + ImGui::TableSetColumnIndex(2); + ImGui::PushItemWidth(-1); ImGui::InputText("##desc", item.description, IM_ARRAYSIZE(item.description)); + ImGui::PopItemWidth(); - ImGui::TableSetColumnIndex(2); + ImGui::TableSetColumnIndex(3); + ImGui::PushItemWidth(-1); ImGui::InputFloat("##price", &item.net_per_item, 0.0f, 0.0f, "%.2f"); + ImGui::PopItemWidth(); - ImGui::TableSetColumnIndex(3); + ImGui::TableSetColumnIndex(4); ImGui::InputFloat("##discount", &item.discount, 0.0f, 0.0f, "%.0f"); ImGui::SameLine(); @@ -295,16 +306,18 @@ static void draw_invoice_items_form(invoice* invoice) } } - ImGui::TableSetColumnIndex(4); + ImGui::TableSetColumnIndex(5); ImGui::Text("%.2f %s", item.net, item.currency); - ImGui::TableSetColumnIndex(5); + ImGui::TableSetColumnIndex(6); + ImGui::PushItemWidth(-1); draw_tax_bracket_selector(item.tax_bracket_id); + ImGui::PopItemWidth(); - ImGui::TableSetColumnIndex(6); + ImGui::TableSetColumnIndex(7); ImGui::Text("%.2f %s", item.tax, item.currency); - ImGui::TableSetColumnIndex(7); + ImGui::TableSetColumnIndex(8); ImGui::Text("%.2f %s", item.total, item.currency); ImGui::PopID(); @@ -312,6 +325,18 @@ static void draw_invoice_items_form(invoice* invoice) administration_update_billing_item_of_invoice(invoice, item); } + ImGui::TableNextRow(); + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(70, 70, 70, 255)); + + ImGui::TableSetColumnIndex(5); + ImGui::Text("%.2f %s", invoice->net, invoice->currency); + + ImGui::TableSetColumnIndex(7); + ImGui::Text("%.2f %s", invoice->tax, invoice->currency); + + ImGui::TableSetColumnIndex(8); + ImGui::Text("%.2f %s", invoice->total, invoice->currency); + ImGui::EndTable(); } } @@ -323,7 +348,6 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) // 1. Identifier //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); @@ -332,7 +356,6 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) ImGui::Text("Supplier: %s", buffer->supplier.name); // 4. Invoice issued at - ImGui::BeginDisabled(); tm issued_at_date = *gmtime(&buffer->issued_at); if (ImGui::DatePicker("##issuedAt", issued_at_date)) { @@ -340,10 +363,8 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) } ImGui::SameLine(); ImGui::Text("Invoice issued at"); - ImGui::EndDisabled(); // 5. Invoice expires at - ImGui::BeginDisabled(); tm expires_at_date = *gmtime(&buffer->expires_at); if (ImGui::DatePicker("##expiresAt", expires_at_date)) { @@ -374,22 +395,21 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) } // 8. (optional) shipping address. - static bool shipping_is_billing_addr = true; - ImGui::Checkbox("Shipping information is billing information", &shipping_is_billing_addr); - if (!shipping_is_billing_addr) { + ImGui::Checkbox("Shipping information differs from billing information (triangulation)", &buffer->is_triangulation); + if (buffer->is_triangulation) { ImGui::Spacing(); ImGui::Text("Shipping information"); - ui_draw_address_form(&buffer->shipping_address); + draw_contact_form_ex(&buffer->addressee, 0,0,0); } ImGui::Separator(); // 9. Project selection - draw_project_selector(buffer->project_id); + //draw_project_selector(buffer->project_id); // 10. Cost center selection - draw_costcenter_selector(buffer->cost_center_id); + //draw_costcenter_selector(buffer->cost_center_id); + //ImGui::Separator(); - ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); @@ -412,12 +432,8 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) // 13. Invoice items form draw_invoice_items_form(buffer); - - ImGui::Separator(); - - // 14. Totals overview. - if (viewing_only) ImGui::EndDisabled(); + //if (viewing_only) ImGui::EndDisabled(); } void draw_invoices_list() @@ -432,23 +448,32 @@ void draw_invoices_list() } } +static void ui_draw_invoice_create() +{ + if (ImGui::Button(localize("form.back"))) { + current_view_state = view_state::LIST; + } + + draw_invoice_form(&active_invoice); + + bool can_save = administration_is_invoice_valid(&active_invoice); + if (!can_save) ImGui::BeginDisabled(); + + ImGui::Spacing(); + if (ImGui::Button(localize("form.save"))) { + administration_add_invoice(&active_invoice); + current_view_state = view_state::LIST; + } + + if (!can_save) ImGui::EndDisabled(); +} + void ui_draw_invoices() { switch(current_view_state) { case view_state::LIST: draw_invoices_list(); break; - 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::CREATE: ui_draw_invoice_create(); break; case view_state::EDIT: break; case view_state::VIEW: break; } |
