diff options
| -rw-r--r-- | docs/CHANGES.rst | 2 | ||||
| -rw-r--r-- | docs/README.rst | 2 | ||||
| -rw-r--r-- | include/administration.hpp | 1 | ||||
| -rw-r--r-- | include/config.hpp | 7 | ||||
| -rw-r--r-- | include/file_templates.hpp | 8 | ||||
| -rw-r--r-- | include/ui.hpp | 18 | ||||
| -rw-r--r-- | src/administration.cpp | 13 | ||||
| -rw-r--r-- | src/administration_writer.cpp | 34 | ||||
| -rw-r--r-- | src/config.cpp | 24 | ||||
| -rw-r--r-- | src/ui/helpers.cpp | 74 | ||||
| -rw-r--r-- | src/ui/imgui_extensions.cpp | 414 | ||||
| -rw-r--r-- | src/ui/ui_contacts.cpp | 163 | ||||
| -rw-r--r-- | src/ui/ui_expenses.cpp | 123 | ||||
| -rw-r--r-- | src/ui/ui_invoices.cpp | 193 | ||||
| -rw-r--r-- | src/ui/ui_projects.cpp | 17 | ||||
| -rw-r--r-- | tests/peppol_write_tests.cpp | 86 |
16 files changed, 620 insertions, 559 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst index 6611e50..8701d77 100644 --- a/docs/CHANGES.rst +++ b/docs/CHANGES.rst @@ -1,6 +1,8 @@ .. _changes: TODO: +- get rid of billing item properties: invoice_id and tax_rate_id. replace tax_rate_id with copy of tax rate +- get rid of customer_id and supplier_id. Just check for existing contact entries when finishing invoice and create new contact if necessary. - validate billing items in invoice add/update - project start and end date should be stored as YYYY-MM-DD - Send invoice by email diff --git a/docs/README.rst b/docs/README.rst index c92bb7a..fcd228d 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -38,6 +38,8 @@ This section lists all supported countries. If the country you operate from is n 3. Dependencies --------------- +Changes/patches have been made to most of these libraries. + - ImGui 1.92.1 (https://github.com/ocornut/imgui) - simclist 1.5 (https://mij.oltrelinux.com/devel/simclist/) - ImGuiDatePicker (https://github.com/DnA-IntRicate/ImGuiDatePicker) diff --git a/include/administration.hpp b/include/administration.hpp index cfdb293..0d15195 100644 --- a/include/administration.hpp +++ b/include/administration.hpp @@ -367,6 +367,7 @@ typedef struct #define A_ERR_MISSING_BUSINESSID (1ULL << 14) #define A_ERR_MAX_ITEMS_REACHED (1ULL << 15) #define A_ERR_MISSING_CODE (1ULL << 16) +#define A_ERR_MISSING_EMAIL (1ULL << 17) typedef uint32_t a_err; diff --git a/include/config.hpp b/include/config.hpp index 2284c8a..9598841 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -29,4 +29,9 @@ #define u8 uint8_t #define u16 uint16_t #define u32 uint32_t -#define u64 uint64_t
\ No newline at end of file +#define u64 uint64_t + +extern const char* country_codes[]; +extern s32 country_count; + +#define COLOR_ERROR_OUTLINE IM_COL32(255, 0, 0, 80)
\ No newline at end of file diff --git a/include/file_templates.hpp b/include/file_templates.hpp index 1980d97..9064abf 100644 --- a/include/file_templates.hpp +++ b/include/file_templates.hpp @@ -84,14 +84,10 @@ const char* peppol_invoice_line_template = " <cbc:InvoicedQuantity unitCode=\"{{UNIT_CODE}}\">{{QUANTITY}}</cbc:InvoicedQuantity>\n" " <cbc:LineExtensionAmount currencyID=\"{{CURRENCY}}\">{{LINE_AMOUNT}}</cbc:LineExtensionAmount>\n" -" <cac:OrderLineReference>\n" -" <cbc:LineID>{{TAX_BRACKET_ID}}</cbc:LineID>\n" -" </cac:OrderLineReference>\n" - " <cac:AllowanceCharge>\n" " <cbc:ChargeIndicator>false</cbc:ChargeIndicator>\n" " <cbc:AllowanceChargeReason>Discount</cbc:AllowanceChargeReason>\n" -" {{ALLOWANCE_IS_PERCENTAGE}}\n" +" <cbc:MultiplierFactorNumeric>{{DISCOUNT_TOTAL_PERCENTAGE}}</cbc:MultiplierFactorNumeric>\n" " <cbc:Amount currencyID=\"{{CURRENCY}}\">{{DISCOUNT_TOTAL}}</cbc:Amount>\n" " <cbc:BaseAmount currencyID=\"{{CURRENCY}}\">{{DISCOUNT_BASE_AMOUNT}}</cbc:BaseAmount>\n" " </cac:AllowanceCharge>\n" @@ -168,7 +164,6 @@ const char *peppol_invoice_template = "\n" " <cac:PartyLegalEntity>\n" " <cbc:RegistrationName>{{SUPPLIER_LEGAL_NAME}}</cbc:RegistrationName>\n" -" <cbc:CompanyID schemeID=\"ZZZ\">{{SUPPLIER_BUSINESS_ID}}</cbc:CompanyID>\n" " </cac:PartyLegalEntity>\n" "\n" " <cac:Contact>\n" @@ -208,7 +203,6 @@ const char *peppol_invoice_template = "\n" " <cac:PartyLegalEntity>\n" " <cbc:RegistrationName>{{CUSTOMER_LEGAL_NAME}}</cbc:RegistrationName>\n" -" <cbc:CompanyID schemeID=\"ZZZ\">{{CUSTOMER_BUSINESS_ID}}</cbc:CompanyID>\n" " </cac:PartyLegalEntity>\n" "\n" " <cac:Contact>\n" diff --git a/include/ui.hpp b/include/ui.hpp index cdffa58..d6c5fad 100644 --- a/include/ui.hpp +++ b/include/ui.hpp @@ -17,6 +17,7 @@ #pragma once #include "imgui.h" +#include "administration.hpp" #define STATUS_TEXT_LEN 64 #define STATUS_DURATION 4.0f @@ -61,8 +62,6 @@ typedef struct extern ImFont* fontBold; extern ImFont* fontBig; -void ui_helper_draw_required_tag(); - ui_status ui_get_status(); void ui_set_status_loading(bool loading); void ui_set_status_error(const char* txt); @@ -94,4 +93,17 @@ void ui_destroy_expenses(); void ui_destroy_earnings(); // 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 +namespace ImGui +{ + int TextInputWithAutocomplete(const char* hint, char* buffer, size_t buf_size, char** suggestions, int suggestion_count, bool has_error); + + void FormContactAutocomplete(contact* buffer, bool has_error); + void FormInputTextWithErrorHint(const char* hint, void* data, char* buffer, size_t buf_size, bool has_error); + void FormCountryCombo(void* data, char* buffer, size_t buf_size); + void FormContactTypeCombo(contact_type* type); + void FormCostCenterCombo(char* costcenter_id); + void FormProjectCombo(char* project_id); + void FormTaxRateCombo(char* tax_rate_id, char* orig_country, char* dest_country); + bool FormCurrencyCombo(char* currency); + void FormToggleCombo(bool *buffer, char* option1, char* option2); +}
\ No newline at end of file diff --git a/src/administration.cpp b/src/administration.cpp index f8d53f9..09bb560 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -126,8 +126,8 @@ static void administration_get_random_billing_items(invoice* inv) tax_rate buffer[20]; - char* country_codes[1] = {inv->supplier.address.country_code}; - u32 rate_count = administration_tax_rate_get_by_country(buffer, 1, country_codes); + char* tax_country_codes[1] = {inv->supplier.address.country_code}; + u32 rate_count = administration_tax_rate_get_by_country(buffer, 1, tax_country_codes); tax_rate rand_rate = buffer[rand() % rate_count]; strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); @@ -1045,6 +1045,7 @@ a_err administration_contact_is_valid(contact data) { a_err result = A_ERR_SUCCESS; if (strlen(data.name) == 0) result |= A_ERR_MISSING_NAME; + if (strlen(data.email) == 0) result |= A_ERR_MISSING_EMAIL; if (strlen(data.address.city) == 0) result |= A_ERR_MISSING_CITY; if (strlen(data.address.postal) == 0) result |= A_ERR_MISSING_POSTAL; if (strlen(data.address.address1) == 0) result |= A_ERR_MISSING_ADDRESS1; @@ -1326,7 +1327,7 @@ a_err administration_tax_rate_add(tax_rate data) return A_ERR_SUCCESS; } -u32 administration_tax_rate_get_by_country(tax_rate* buffer, u32 code_count, char** country_codes) +u32 administration_tax_rate_get_by_country(tax_rate* buffer, u32 code_count, char** tax_country_codes) { assert(buffer); @@ -1342,7 +1343,7 @@ u32 administration_tax_rate_get_by_country(tax_rate* buffer, u32 code_count, cha } for (u32 x = 0; x < code_count; x++) { - if (strcmp(c.country_code, country_codes[x]) == 0) { + if (strcmp(c.country_code, tax_country_codes[x]) == 0) { buffer[write_cursor++] = c; continue; } @@ -1708,7 +1709,7 @@ static void administration_invoice_set_refs(invoice* inv) } else { - // Store customer id in invoice, (only id is stored to disk, supplier field is filled on load). + // Store customer id in invoice. strops_copy(inv->customer_id, inv->customer.id, sizeof(inv->customer_id)); } } @@ -1772,7 +1773,7 @@ a_err administration_invoice_add(invoice* inv) memcpy(new_inv, ©, sizeof(invoice)); - new_inv->payment_means.payment_method = PAYMENT_METHOD_DEBIT_TRANSFER; + new_inv->payment_means.payment_method = PAYMENT_METHOD_STANDING_AGREEMENT; strops_copy(new_inv->payment_means.payee_bank_account, inv->customer.bank_account, sizeof(new_inv->payment_means.payee_bank_account)); strops_copy(new_inv->payment_means.payee_account_name, inv->customer.name, sizeof(new_inv->payment_means.payee_account_name)); strops_copy(new_inv->payment_means.service_provider_id, "", sizeof(new_inv->payment_means.service_provider_id)); diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp index 4722f7c..e7d9e07 100644 --- a/src/administration_writer.cpp +++ b/src/administration_writer.cpp @@ -155,8 +155,13 @@ static bool administration_writer_write_to_zip(char* entry_to_replace, char* ori ///////////////////////////// //// Invoices ///////////////////////////// + static char* administration_writer_get_eas_id_for_contact(contact contact) { + if (contact.type == contact_type::CONTACT_CONSUMER) { + return "[CONSUMER]"; + } + // https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/ char* country_code = contact.address.country_code; @@ -176,7 +181,6 @@ static char* administration_writer_get_eas_id_for_contact(contact contact) if (strcmp(country_code, "LU") == 0) return contact.taxid; // Luxembourg if (strcmp(country_code, "LV") == 0) return contact.taxid; // Latvia if (strcmp(country_code, "MT") == 0) return contact.taxid; // Malta - if (strcmp(country_code, "NL") == 0) return contact.taxid; // Netherlands if (strcmp(country_code, "PL") == 0) return contact.taxid; // Poland if (strcmp(country_code, "PT") == 0) return contact.taxid; // Portugal if (strcmp(country_code, "RO") == 0) return contact.taxid; // Romania @@ -185,6 +189,7 @@ static char* administration_writer_get_eas_id_for_contact(contact contact) if (strcmp(country_code, "ES") == 0) return contact.taxid; // Spain // Countries using business identification numbers. + if (strcmp(country_code, "NL") == 0) return contact.businessid; // Netherlands if (strcmp(country_code, "SE") == 0) return contact.businessid; // Sweden if (strcmp(country_code, "LT") == 0) return contact.businessid; // Lithuania if (strcmp(country_code, "IT") == 0) return contact.businessid; // Italy @@ -193,8 +198,14 @@ static char* administration_writer_get_eas_id_for_contact(contact contact) return NULL; // Unknown country code } -static char* administration_writer_get_eas_scheme_for_address(address addr) +static char* administration_writer_get_eas_scheme_for_contact(contact contact) { + if (contact.type == contact_type::CONTACT_CONSUMER) { + return "0203"; // Hack + } + + address addr = contact.address; + // https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/ char* country_code = addr.country_code; if (strcmp(country_code, "AT") == 0) return "9914"; // Austria @@ -216,7 +227,7 @@ static char* administration_writer_get_eas_scheme_for_address(address addr) if (strcmp(country_code, "LU") == 0) return "9938"; // Luxembourg if (strcmp(country_code, "LV") == 0) return "9939"; // Latvia if (strcmp(country_code, "MT") == 0) return "9943"; // Malta - if (strcmp(country_code, "NL") == 0) return "9944"; // Netherlands + if (strcmp(country_code, "NL") == 0) return "0106"; // Netherlands if (strcmp(country_code, "PL") == 0) return "9945"; // Poland if (strcmp(country_code, "PT") == 0) return "9946"; // Portugal if (strcmp(country_code, "RO") == 0) return "9947"; // Romania @@ -309,7 +320,7 @@ bool administration_writer_save_invoice_blocking(invoice inv) strops_replace(file_content, buf_length, "{{INVOICE_DOCUMENT}}", inv.document); // Supplier data - strops_replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_address(inv.supplier.address)); + strops_replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_contact(inv.supplier)); strops_replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_ID}}", administration_writer_get_eas_id_for_contact(inv.supplier)); strops_replace(file_content, buf_length, "{{SUPPLIER_ID}}", inv.supplier.id); strops_replace(file_content, buf_length, "{{SUPPLIER_NAME}}", inv.supplier.name); @@ -326,7 +337,7 @@ bool administration_writer_save_invoice_blocking(invoice inv) strops_replace(file_content, buf_length, "{{SUPPLIER_EMAIL}}", inv.supplier.email); // Customer data - strops_replace(file_content, buf_length, "{{CUSTOMER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_address(inv.customer.address)); + strops_replace(file_content, buf_length, "{{CUSTOMER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_contact(inv.customer)); strops_replace(file_content, buf_length, "{{CUSTOMER_ENDPOINT_ID}}", administration_writer_get_eas_id_for_contact(inv.customer)); strops_replace(file_content, buf_length, "{{CUSTOMER_ID}}", inv.customer.id); strops_replace(file_content, buf_length, "{{CUSTOMER_NAME}}", inv.customer.name); @@ -431,19 +442,18 @@ bool administration_writer_save_invoice_blocking(invoice inv) strops_replace_float(billing_item_file_content, billing_item_buf_length, "{{QUANTITY}}", bi.amount, 2); strops_replace_float(billing_item_file_content, billing_item_buf_length, "{{UNIT_PRICE}}", bi.net_per_item, 2); // unit price before discount strops_replace(billing_item_file_content, billing_item_buf_length, "{{UNIT_CODE}}", bi.amount_is_percentage ? "%" : "X"); - strops_replace(billing_item_file_content, billing_item_buf_length, "{{TAX_BRACKET_ID}}", bi.tax_rate_id); - + //strops_replace(billing_item_file_content, billing_item_buf_length, "{{TAX_BRACKET_ID}}", bi.tax_rate_id); + if (bi.discount_is_percentage) { - strops_replace(billing_item_file_content, billing_item_buf_length, "{{ALLOWANCE_IS_PERCENTAGE}}", - "<cbc:MultiplierFactorNumeric>{{DISCOUNT_TOTAL_PERCENTAGE}}</cbc:MultiplierFactorNumeric>"); - strops_replace_float(billing_item_file_content, billing_item_buf_length, "{{DISCOUNT_TOTAL_PERCENTAGE}}", bi.allowance / (bi.net + bi.allowance), 2); + strops_replace_float(billing_item_file_content, billing_item_buf_length, "{{DISCOUNT_TOTAL_PERCENTAGE}}", (bi.allowance / (bi.net + bi.allowance)) * 100.0f, 2); + strops_replace_float(billing_item_file_content, billing_item_buf_length, "{{DISCOUNT_BASE_AMOUNT}}", bi.net + bi.allowance, 2); // Total net before discount. } else { - strops_replace(billing_item_file_content, billing_item_buf_length, "{{ALLOWANCE_IS_PERCENTAGE}}", ""); + strops_replace(billing_item_file_content, billing_item_buf_length, "{{DISCOUNT_TOTAL_PERCENTAGE}}", ""); + strops_replace(billing_item_file_content, billing_item_buf_length, "{{DISCOUNT_BASE_AMOUNT}}", ""); } strops_replace_float(billing_item_file_content, billing_item_buf_length, "{{DISCOUNT_TOTAL}}", bi.allowance, 2); - strops_replace_float(billing_item_file_content, billing_item_buf_length, "{{DISCOUNT_BASE_AMOUNT}}", bi.net + bi.allowance, 2); // Total net before discount. u32 content_len = (u32)strlen(billing_item_file_content); memcpy(billing_item_list_buffer+billing_item_list_buffer_cursor, billing_item_file_content, content_len); diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..9ab8e90 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2025 Aldrik Ramaekers <aldrik.ramaekers@gmail.com> +* +* Permission to use, copy, modify, and/or distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "config.hpp" + +const char* country_codes[] = { + // "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", + /*"DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT",*/ "NL", + // "PL", "PT", "RO", "SK", "SI", "ES", "SE" +}; +s32 country_count = sizeof(country_codes) / sizeof(country_codes[0]);
\ No newline at end of file diff --git a/src/ui/helpers.cpp b/src/ui/helpers.cpp index 07302b7..2447270 100644 --- a/src/ui/helpers.cpp +++ b/src/ui/helpers.cpp @@ -93,77 +93,3 @@ ui_status ui_get_status() { return current_status; } - -void ui_helper_draw_required_tag() -{ - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - const char* text = localize("form.required"); - ImVec2 text_pos = ImGui::GetCursorScreenPos(); - ImVec2 text_size = ImGui::CalcTextSize(text); - text_pos.y += text_size.y/4.0f; - - ImVec4 bg_color = ImVec4(0.9f, 0.235f, 0.235f, 0.4f); // Red background - ImVec4 text_color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White text - float rounding = 2.0f; - float padding = 2.0f; - - // Background rectangle - ImVec2 bg_min = ImVec2(text_pos.x - padding, text_pos.y - padding); - ImVec2 bg_max = ImVec2(text_pos.x + text_size.x + padding, text_pos.y + text_size.y + padding); - draw_list->AddRectFilled(bg_min, bg_max, ImColor(bg_color), rounding); - - // Foreground text - ImGui::PushStyleColor(ImGuiCol_Text, text_color); - ImGui::TextUnformatted(text); - ImGui::PopStyleColor(); -} - -int 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' && suggestion_count > 0) - { - is_open = true; - } - - if (is_open) { - ImGui::BeginChild("autocomplete_popup", ImVec2(0, 10.0f + suggestion_count*23.0f), true); - { - ImVec2 win_pos = ImGui::GetWindowPos(); - ImVec2 win_size = ImGui::GetWindowSize(); - ImVec2 mouse_pos = ImGui::GetMousePos(); - - bool mouse_clicked_outside = !is_active && ImGui::IsMouseClicked(0) && - (mouse_pos.x < win_pos.x || mouse_pos.x > win_pos.x + win_size.x || - mouse_pos.y < win_pos.y || mouse_pos.y > win_pos.y + win_size.y); - - if (mouse_clicked_outside) is_open = false; - - for (int i = 0; i < suggestion_count; ++i) - { - ImGui::PushID(i); - if (ImGui::Selectable(suggestions[i])) - { - // Copy selected suggestion to buffer - strops_copy(buffer, suggestions[i], buf_size); - buffer[buf_size - 1] = '\0'; - result = i; - is_open = false; - } - ImGui::PopID(); - } - - ImGui::EndChild(); - } - } - return result; -} diff --git a/src/ui/imgui_extensions.cpp b/src/ui/imgui_extensions.cpp new file mode 100644 index 0000000..5fdc3a7 --- /dev/null +++ b/src/ui/imgui_extensions.cpp @@ -0,0 +1,414 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "ui.hpp" +#include "strops.hpp" +#include "config.hpp" +#include "locales.hpp" +#include "administration.hpp" + +namespace ImGui +{ + void FormInputTextWithErrorHint(const char* hint, void* data, char* buffer, size_t buf_size, bool has_error) + { + float widthAvailable = ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(widthAvailable*0.5f); + + char id[MAX_LEN_LONG_DESC]; + snprintf(id, sizeof(id), "%s##%p", hint, data); + + if (has_error) { + ImGui::PushStyleColor(ImGuiCol_Border, COLOR_ERROR_OUTLINE); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); + } + ImGui::InputTextWithHint(id, hint, buffer, buf_size); + if (has_error) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x); + ImGui::TextColored(ImVec4(1, 0, 0, 1), "*"); + } + } + + void FormCountryCombo(void* data, char* buffer, size_t buf_size) + { + const char* selected_country = 0; + char id[MAX_LEN_LONG_DESC]; + float widthAvailable = ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(widthAvailable*0.5f); + + const char* countries[30]; + for (int x = 0; x < country_count; x++) + { + char locale_str[20]; + snprintf(locale_str, 20, "country.%s", country_codes[x]); + countries[x] = localize(locale_str); + } + + for (int i = 0; i < country_count; i++) + { + if (strcmp(country_codes[i], buffer) == 0) + { + selected_country = countries[i]; + break; + } + } + bool has_a_selection = selected_country != 0; + + int selected_country_index = -1; + snprintf(id, sizeof(id), "%s##%p", localize("contact.form.country"), data); + + if (!has_a_selection) { + ImGui::PushStyleColor(ImGuiCol_Border, COLOR_ERROR_OUTLINE); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); + } + + if (ImGui::BeginCombo(id, selected_country)) + { + for (int n = 0; n < country_count; n++) + { + bool is_selected = (selected_country == countries[n]); + if (ImGui::Selectable(countries[n], is_selected)) { + selected_country = countries[n]; + selected_country_index = n; + } + } + ImGui::EndCombo(); + } + + if (!has_a_selection) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x); + ImGui::TextColored(ImVec4(1, 0, 0, 1), "*"); + } + + if (selected_country_index != -1) { + strops_copy(buffer, country_codes[selected_country_index], buf_size); + } + } + + void FormContactTypeCombo(contact_type* type) + { + float widthAvailable = ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(widthAvailable*0.5f); + const char* customer_types[2] = { localize("contact.form.type.business"), localize("contact.form.type.consumer") }; + int currentItem = static_cast<int>(*type); + if (ImGui::Combo(localize("contact.form.type"), ¤tItem, customer_types, IM_ARRAYSIZE(customer_types))) + { + *type = static_cast<contact_type>(currentItem); + } + } + + + int TextInputWithAutocomplete(const char* hint, char* buffer, size_t buf_size, char* suggestions[], int suggestion_count, bool has_error) + { + int result = -1; + static bool is_open = false; + + if (has_error) { + ImGui::PushStyleColor(ImGuiCol_Border, COLOR_ERROR_OUTLINE); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); + } + ImGui::InputTextWithHint(hint, hint, buffer, buf_size); + if (has_error) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x); + ImGui::TextColored(ImVec4(1, 0, 0, 1), "*"); + } + + + 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' && suggestion_count > 0) + { + is_open = true; + } + + if (is_open) { + ImGui::BeginChild("autocomplete_popup", ImVec2(0, 10.0f + suggestion_count*23.0f), true); + { + ImVec2 win_pos = ImGui::GetWindowPos(); + ImVec2 win_size = ImGui::GetWindowSize(); + ImVec2 mouse_pos = ImGui::GetMousePos(); + + bool mouse_clicked_outside = !is_active && ImGui::IsMouseClicked(0) && + (mouse_pos.x < win_pos.x || mouse_pos.x > win_pos.x + win_size.x || + mouse_pos.y < win_pos.y || mouse_pos.y > win_pos.y + win_size.y); + + if (mouse_clicked_outside) is_open = false; + + for (int i = 0; i < suggestion_count; ++i) + { + ImGui::PushID(i); + if (ImGui::Selectable(suggestions[i])) + { + // Copy selected suggestion to buffer + strops_copy(buffer, suggestions[i], buf_size); + buffer[buf_size - 1] = '\0'; + result = i; + is_open = false; + } + ImGui::PopID(); + } + + ImGui::EndChild(); + } + } + return result; + } + + void FormContactAutocomplete(contact* buffer, bool has_error) + { + float widthAvailable = ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(widthAvailable*0.5f); + + contact autocomplete_list[5]; + int autocomplete_count = administration_contact_get_autocompletions(autocomplete_list, 5, buffer->name); + char* autocomplete_strings[5]; + + for (int i = 0; i < autocomplete_count; i++) + { + 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 = ImGui::TextInputWithAutocomplete(localize("contact.form.fullname"), + buffer->name, IM_ARRAYSIZE(buffer->name), (char**)autocomplete_strings, autocomplete_count, has_error); + + if (autocomplete_index != -1) + { + memcpy(buffer, &autocomplete_list[autocomplete_index], sizeof(contact)); + } + + for (int i = 0; i < autocomplete_count; i++) + { + free(autocomplete_strings[i]); + } + } + + void FormCostCenterCombo(char* costcenter_id) + { + u32 costcenter_count = administration_cost_center_count(); + cost_center* buffer = (cost_center*) malloc(sizeof(cost_center) * costcenter_count); + + cost_center* selected_costcenter = NULL; + costcenter_count = administration_cost_center_get_all(buffer); + + // Select cost center by given id. + if (strlen(costcenter_id) > 0) + { + for (u32 i = 0; i < costcenter_count; i++) + { + if (strcmp(buffer[i].id, costcenter_id) == 0) + { + selected_costcenter = &buffer[i]; + break; + } + } + } + + int selected_costcenter_index = -1; + if (ImGui::BeginCombo(localize("invoice.form.costcenter"), selected_costcenter == NULL ? NULL : localize(selected_costcenter->description))) + { + for (u32 n = 0; n < costcenter_count; n++) + { + bool is_selected = selected_costcenter && strcmp(selected_costcenter->id, buffer[n].id) == 0; + if (ImGui::Selectable(localize(buffer[n].description), is_selected)) { + selected_costcenter_index = n; + } + } + ImGui::EndCombo(); + } + + if (selected_costcenter_index != -1) { + strops_copy(costcenter_id, buffer[selected_costcenter_index].id, MAX_LEN_ID); + } + + free(buffer); + } + + void FormProjectCombo(char* project_id) + { + u32 project_count = administration_project_count(); + project* buffer = (project*) malloc(sizeof(project) * project_count); + + project* selected_project = NULL; + project_count = administration_project_get_all(buffer); + + // Select project by given id. + if (strlen(project_id) > 0) + { + for (u32 i = 0; i < project_count; i++) + { + if (strcmp(buffer[i].id, project_id) == 0) + { + selected_project = &buffer[i]; + break; + } + } + } + + int selected_project_index = -1; + if (ImGui::BeginCombo(localize("invoice.form.project"), selected_project == NULL ? NULL : selected_project->description)) + { + for (u32 n = 0; n < project_count; n++) + { + bool is_selected = selected_project && strcmp(selected_project->id, buffer[n].id) == 0; + if (ImGui::Selectable(buffer[n].description, is_selected)) { + selected_project_index = n; + } + } + ImGui::EndCombo(); + } + + if (selected_project_index != -1) { + strops_copy(project_id, buffer[selected_project_index].id, MAX_LEN_ID); + } + + free(buffer); + } + + void FormTaxRateCombo(char* tax_rate_id, char* orig_country, char* dest_country) + { + u32 tax_rate_count = administration_tax_rate_count(); + tax_rate* buffer = (tax_rate*) malloc(sizeof(tax_rate) * tax_rate_count); + + tax_rate* selected_tax_rate = NULL; + char* tax_country_codes[2] = {orig_country, dest_country}; + tax_rate_count = administration_tax_rate_get_by_country(buffer, strcmp(orig_country, dest_country) == 0 ? 1 : 2, tax_country_codes); + + // Select tax rate by given id. + if (strlen(tax_rate_id) > 0) + { + for (u32 i = 0; i < tax_rate_count; i++) + { + if (strcmp(buffer[i].id, tax_rate_id) == 0) + { + selected_tax_rate = &buffer[i]; + break; + } + } + } + + int selected_tax_rate_index = -1; + char rate_str_buf[40]; + rate_str_buf[0] = 0; + if (selected_tax_rate) + { + if (strcmp(selected_tax_rate->country_code, "00") == 0) { + char category_code_desc[MAX_LEN_LONG_DESC]; + snprintf(category_code_desc, MAX_LEN_LONG_DESC, "taxcategory.%s", selected_tax_rate->category_code); + snprintf(rate_str_buf, 40, "%s", localize(category_code_desc)); + } + else snprintf(rate_str_buf, 40, "%s/%.1f%%", selected_tax_rate->country_code, selected_tax_rate->rate); + } + + if (ImGui::BeginCombo("##Tax Bracket", rate_str_buf)) + { + for (u32 n = 0; n < tax_rate_count; n++) + { + bool is_selected = selected_tax_rate && strcmp(selected_tax_rate->id, buffer[n].id) == 0; + + if (strcmp(buffer[n].country_code, "00") == 0) { + char category_code_desc[MAX_LEN_LONG_DESC]; + snprintf(category_code_desc, MAX_LEN_LONG_DESC, "taxcategory.%s", buffer[n].category_code); + snprintf(rate_str_buf, 40, "%s", localize(category_code_desc)); + } + else snprintf(rate_str_buf, 40, "%s/%.1f%%", buffer[n].country_code, buffer[n].rate); + + if (ImGui::Selectable(rate_str_buf, is_selected)) { + selected_tax_rate_index = n; + } + } + ImGui::EndCombo(); + } + + if (selected_tax_rate_index != -1) { + strops_copy(tax_rate_id, buffer[selected_tax_rate_index].id, MAX_LEN_ID); + } + + free(buffer); + } + + bool FormCurrencyCombo(char* currency) + { + int currentCurrency = 0; + bool result = false; + + // Top 15 most traded currencies + all EU official currencies + const char* currencies[] = { + // Top 15 + "EUR", "USD", "JPY", "GBP", "AUD", "CAD", "CHF", + "CNY", "HKD", "NZD", "SEK", "KRW", "SGD", "NOK", "MXN", + + // Additional EU currencies + "BGN", // Bulgarian Lev + "CZK", // Czech Koruna + "DKK", // Danish Krone + "HUF", // Hungarian Forint + "PLN", // Polish Zloty + "RON", // Romanian Leu + // "HRK", // Croatian Kuna (legacy, replaced by EUR in 2023) + }; + int currency_count = sizeof(currencies) / sizeof(char*); + + if (strlen(currency) > 0) + { + for (int i = 0; i < currency_count; i++) + { + if (strcmp(currencies[i], currency) == 0) + { + currentCurrency = i; + break; + } + } + } + + ImGui::SetNextItemWidth(100.0f); + if (ImGui::BeginCombo("##currency", currencies[currentCurrency])) + { + for (int n = 0; n < IM_ARRAYSIZE(currencies); n++) + { + bool isSelected = (currentCurrency == n); + if (ImGui::Selectable(currencies[n], isSelected)) + { + result = true; + strops_copy(currency, currencies[n], MAX_LEN_CURRENCY); + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + return result; + } + + void FormToggleCombo(bool *buffer, char* option1, char* option2) + { + const char* items[] = { option1, option2 }; + if (ImGui::BeginCombo("Mode", items[*buffer])) { + for (int n = 0; n < 2; n++) { + bool is_selected = (n == (int)*buffer); + if (ImGui::Selectable(items[n], is_selected)) { + *buffer = n; + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + } +}
\ No newline at end of file diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp index 92dd21a..b471f58 100644 --- a/src/ui/ui_contacts.cpp +++ b/src/ui/ui_contacts.cpp @@ -18,6 +18,7 @@ #include <stdlib.h> #include "strops.hpp" +#include "config.hpp" #include "ui.hpp" #include "imgui.h" #include "administration.hpp" @@ -29,76 +30,15 @@ static contact selected_for_removal; static contact active_contact; -void ui_draw_address_form(address* buffer) +void ui_draw_address_form(address* buffer, a_err last_err) { - const char* selected_country = NULL; - float widthAvailable = ImGui::GetContentRegionAvail().x; - - char id[MAX_LEN_ADDRESS]; - - ImGui::SetNextItemWidth(widthAvailable*0.5f); - snprintf(id, sizeof(id), "%s##%p", localize("contact.form.address1"), buffer); - ImGui::InputTextWithHint(id, localize("contact.form.address1"), buffer->address1, IM_ARRAYSIZE(buffer->address1)); - ImGui::SameLine();ui_helper_draw_required_tag(); - - ImGui::SetNextItemWidth(widthAvailable*0.5f); - snprintf(id, sizeof(id), "%s##%p", localize("contact.form.address2"), buffer); - ImGui::InputTextWithHint(id, localize("contact.form.address2"), buffer->address2, IM_ARRAYSIZE(buffer->address2)); - //ImGui::SameLine();ui_helper_draw_required_tag(); - - ImGui::SetNextItemWidth(widthAvailable*0.5f); - snprintf(id, sizeof(id), "%s##%p", localize("contact.form.city"), buffer); - ImGui::InputTextWithHint(id, localize("contact.form.city"), buffer->city, IM_ARRAYSIZE(buffer->city)); - ImGui::SameLine();ui_helper_draw_required_tag(); - - ImGui::SetNextItemWidth(widthAvailable*0.5f); - snprintf(id, sizeof(id), "%s##%p", localize("contact.form.postal"), buffer); - ImGui::InputTextWithHint(id, localize("contact.form.postal"), buffer->postal, IM_ARRAYSIZE(buffer->postal)); - ImGui::SameLine();ui_helper_draw_required_tag(); - - ImGui::SetNextItemWidth(widthAvailable*0.5f); - snprintf(id, sizeof(id), "%s##%p", localize("contact.form.region"), buffer); - ImGui::InputTextWithHint(id, localize("contact.form.region"), buffer->region, IM_ARRAYSIZE(buffer->region)); - ImGui::SameLine();ui_helper_draw_required_tag(); - - // 5. Country dropdown. - ImGui::SetNextItemWidth(widthAvailable*0.5f); - const char* countries[] = { localize("country.AT"),localize("country.BE"),localize("country.BG"),localize("country.HR"),localize("country.CY"),localize("country.CZ"),localize("country.DK"),localize("country.EE"),localize("country.FI"),localize("country.FR"),localize("country.DE"),localize("country.GR"),localize("country.HU"),localize("country.IE"),localize("country.IT"),localize("country.LV"),localize("country.LT"),localize("country.LU"),localize("country.MT"),localize("country.NL"),localize("country.PL"),localize("country.PT"),localize("country.RO"),localize("country.SK"),localize("country.SI"),localize("country.ES"),localize("country.SE") }; - const char* country_codes[] = { - "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", - "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", - "PL", "PT", "RO", "SK", "SI", "ES", "SE" - }; - s32 country_count = sizeof(countries) / sizeof(countries[0]); - if (selected_country == 0) { - for (int i = 0; i < country_count; i++) - { - if (strcmp(country_codes[i], buffer->country_code) == 0) - { - selected_country = countries[i]; - break; - } - } - } + ImGui::FormInputTextWithErrorHint(localize("contact.form.address1"), buffer, buffer->address1, IM_ARRAYSIZE(buffer->address1), last_err & A_ERR_MISSING_ADDRESS1); + ImGui::FormInputTextWithErrorHint(localize("contact.form.address2"), buffer, buffer->address2, IM_ARRAYSIZE(buffer->address2), 0); + ImGui::FormInputTextWithErrorHint(localize("contact.form.city"), buffer, buffer->city, IM_ARRAYSIZE(buffer->city), last_err & A_ERR_MISSING_CITY); + ImGui::FormInputTextWithErrorHint(localize("contact.form.postal"), buffer, buffer->postal, IM_ARRAYSIZE(buffer->postal), last_err & A_ERR_MISSING_POSTAL); + ImGui::FormInputTextWithErrorHint(localize("contact.form.region"), buffer, buffer->region, IM_ARRAYSIZE(buffer->region), 0); - int selected_country_index = -1; - snprintf(id, sizeof(id), "%s##%p", localize("contact.form.country"), buffer); - if (ImGui::BeginCombo(id, selected_country)) - { - for (int n = 0; n < IM_ARRAYSIZE(countries); n++) - { - bool is_selected = (selected_country == countries[n]); - if (ImGui::Selectable(countries[n], is_selected)) { - selected_country = countries[n]; - selected_country_index = n; - } - } - ImGui::EndCombo(); - } - if (selected_country_index != -1) { - strops_copy(buffer->country_code, country_codes[selected_country_index], IM_ARRAYSIZE(buffer->country_code)); - } - ImGui::SameLine();ui_helper_draw_required_tag(); + ImGui::FormCountryCombo(buffer, buffer->country_code, IM_ARRAYSIZE(buffer->country_code)); } void ui_setup_contacts() @@ -108,89 +48,34 @@ void ui_setup_contacts() memset(&selected_for_removal, 0, sizeof(contact)); } -void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0) +void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false) { + a_err last_err = administration_contact_is_valid(*buffer); + ImGui::PushID(buffer); ImGui::Spacing(); - float widthAvailable = ImGui::GetContentRegionAvail().x; - ImGui::BeginDisabled(); - // 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. Full name - ImGui::SetNextItemWidth(widthAvailable*0.5f); - if (with_autocomplete) { - contact autocomplete_list[5]; - int autocomplete_count = administration_contact_get_autocompletions(autocomplete_list, 5, buffer->name); - char* autocomplete_strings[5]; - - for (int i = 0; i < autocomplete_count; i++) - { - 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"), - buffer->name, IM_ARRAYSIZE(buffer->name), (char**)autocomplete_strings, autocomplete_count); - - if (on_autocomplete) { - *on_autocomplete = autocomplete_index != -1; - } + if (with_autocomplete) ImGui::FormContactAutocomplete(buffer, last_err & A_ERR_MISSING_NAME); + else ImGui::FormInputTextWithErrorHint(localize("contact.form.fullname"), buffer, buffer->name, IM_ARRAYSIZE(buffer->name), last_err & A_ERR_MISSING_NAME); - if (autocomplete_index != -1) - { - memcpy(buffer, &autocomplete_list[autocomplete_index], sizeof(contact)); - } + ui_draw_address_form(&buffer->address, last_err); - 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(); - - // 3. Address line 1, 4. address line 2, 5. country - ui_draw_address_form(&buffer->address); - - // 6. Contact type dropdown. - ImGui::SetNextItemWidth(widthAvailable*0.5f); - const char* customer_types[2] = { localize("contact.form.type.business"), localize("contact.form.type.consumer") }; - int currentItem = static_cast<int>(buffer->type); - if (ImGui::Combo(localize("contact.form.type"), ¤tItem, customer_types, IM_ARRAYSIZE(customer_types))) - { - buffer->type = static_cast<contact_type>(currentItem); - } + ImGui::FormContactTypeCombo(&buffer->type); // Fields only required for businesses. if (buffer->type == contact_type::CONTACT_BUSINESS) { - // 7. Tax number - ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.taxnumber"), localize("contact.form.taxnumber"), buffer->taxid, IM_ARRAYSIZE(buffer->taxid)); - - // 8. Business number / Chamber of commerce - ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.businessnumber"), localize("contact.form.businessnumber"), buffer->businessid, IM_ARRAYSIZE(buffer->businessid)); + ImGui::FormInputTextWithErrorHint(localize("contact.form.taxnumber"), buffer, buffer->taxid, IM_ARRAYSIZE(buffer->taxid), last_err & A_ERR_MISSING_TAXID); + ImGui::FormInputTextWithErrorHint(localize("contact.form.businessnumber"), buffer, buffer->businessid, IM_ARRAYSIZE(buffer->businessid), last_err & A_ERR_MISSING_BUSINESSID); } - // 9. Email - ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.email"), localize("contact.form.email"), buffer->email, IM_ARRAYSIZE(buffer->email)); - - // 10. Phone number - ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.phonenumber"), localize("contact.form.phonenumber"), buffer->phone_number, IM_ARRAYSIZE(buffer->phone_number)); - - // 11. Bank account. - ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.bankaccount"), localize("contact.form.bankaccount"), buffer->bank_account, IM_ARRAYSIZE(buffer->bank_account)); + ImGui::FormInputTextWithErrorHint(localize("contact.form.email"), buffer, buffer->email, IM_ARRAYSIZE(buffer->email), last_err & A_ERR_MISSING_EMAIL); + ImGui::FormInputTextWithErrorHint(localize("contact.form.phonenumber"), buffer, buffer->phone_number, IM_ARRAYSIZE(buffer->phone_number), 0); + ImGui::FormInputTextWithErrorHint(localize("contact.form.bankaccount"), buffer, buffer->bank_account, IM_ARRAYSIZE(buffer->bank_account), 0); if (viewing_only) ImGui::EndDisabled(); ImGui::PopID(); @@ -198,7 +83,7 @@ void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_ void draw_contact_form(contact* buffer, bool viewing_only = false) { - draw_contact_form_ex(buffer, viewing_only, false, 0); + draw_contact_form_ex(buffer, viewing_only, false); } @@ -313,7 +198,8 @@ static void ui_draw_contacts_create() draw_contact_form(&active_contact); - bool can_save = administration_contact_is_valid(active_contact) == A_ERR_SUCCESS; + a_err contact_validation_err = administration_contact_is_valid(active_contact); + bool can_save = contact_validation_err == A_ERR_SUCCESS; if (!can_save) ImGui::BeginDisabled(); // Save button ImGui::Spacing(); @@ -332,7 +218,8 @@ static void ui_draw_contacts_update() draw_contact_form(&active_contact); - bool can_save = administration_contact_is_valid(active_contact) == A_ERR_SUCCESS; + a_err contact_validation_err = administration_contact_is_valid(active_contact); + bool can_save = contact_validation_err == A_ERR_SUCCESS; if (!can_save) ImGui::BeginDisabled(); // Save button ImGui::Spacing(); diff --git a/src/ui/ui_expenses.cpp b/src/ui/ui_expenses.cpp index b195442..7d94eaf 100644 --- a/src/ui/ui_expenses.cpp +++ b/src/ui/ui_expenses.cpp @@ -32,94 +32,13 @@ static view_state current_view_state = view_state::LIST; static invoice active_invoice = {0}; static invoice selected_for_removal = {0}; -static cost_center* cost_center_list_buffer = 0; -static tax_rate* tax_rate_list_buffer = 0; -static project* project_list_buffer = 0; static 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 draw_tax_rate_selector(char* tax_rate_id, tax_rate* buffer, char* orig_country, char* dest_country); -bool draw_currency_selector(char* currency); -void draw_invoice_items_form(invoice* invoice, bool is_outgoing); - -void draw_costcenter_selector(char* costcenter_id, cost_center* buffer) -{ - cost_center* selected_costcenter = NULL; - u32 costcenter_count = administration_cost_center_get_all(buffer); - - // Select cost center by given id. - if (strlen(costcenter_id) > 0) - { - for (u32 i = 0; i < costcenter_count; i++) - { - if (strcmp(buffer[i].id, costcenter_id) == 0) - { - selected_costcenter = &buffer[i]; - break; - } - } - } - - int selected_costcenter_index = -1; - if (ImGui::BeginCombo(localize("invoice.form.costcenter"), selected_costcenter == NULL ? NULL : localize(selected_costcenter->description))) - { - for (u32 n = 0; n < costcenter_count; n++) - { - bool is_selected = selected_costcenter && strcmp(selected_costcenter->id, buffer[n].id) == 0; - if (ImGui::Selectable(localize(buffer[n].description), is_selected)) { - selected_costcenter_index = n; - } - } - ImGui::EndCombo(); - } - - if (selected_costcenter_index != -1) { - strops_copy(costcenter_id, buffer[selected_costcenter_index].id, MAX_LEN_ID); - } -} - -void draw_project_selector(char* project_id, project* buffer) -{ - project* selected_project = NULL; - u32 project_count = administration_project_get_all(buffer); - - // Select project by given id. - if (strlen(project_id) > 0) - { - for (u32 i = 0; i < project_count; i++) - { - if (strcmp(buffer[i].id, project_id) == 0) - { - selected_project = &buffer[i]; - break; - } - } - } - - int selected_project_index = -1; - if (ImGui::BeginCombo(localize("invoice.form.project"), selected_project == NULL ? NULL : selected_project->description)) - { - for (u32 n = 0; n < project_count; n++) - { - bool is_selected = selected_project && strcmp(selected_project->id, buffer[n].id) == 0; - if (ImGui::Selectable(buffer[n].description, is_selected)) { - selected_project_index = n; - } - } - ImGui::EndCombo(); - } - - if (selected_project_index != -1) { - strops_copy(project_id, buffer[selected_project_index].id, MAX_LEN_ID); - } -} +void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false); +void draw_invoice_items_form(invoice* invoice); void ui_destroy_expenses() { - free(cost_center_list_buffer); - free(tax_rate_list_buffer); - free(project_list_buffer); free(invoice_items_buffer); } @@ -128,15 +47,6 @@ void ui_setup_expenses() current_view_state = view_state::LIST; 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); - - u32 tax_rate_count = administration_tax_rate_count(); - tax_rate_list_buffer = (tax_rate*) malloc(sizeof(tax_rate) * tax_rate_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); } @@ -145,17 +55,9 @@ static void draw_expense_form(invoice* buffer, bool viewing_only = false) { if (viewing_only) ImGui::BeginDisabled(); - // 1. Identifier - //ImGui::SetNextItemWidth(widthAvailable*0.2f); - //ImGui::InputText(localize("contact.form.identifier"), buffer->id, IM_ARRAYSIZE(buffer->id)); - - // 2. Sequential number ImGui::Text("%s: %s", localize("invoice.form.invoicenumber"), buffer->sequential_number); - - // 3. Billed to (you) ImGui::Text("%s: %s", localize("invoice.form.billedTo"), buffer->customer.name); - // 4. Invoice issued at tm issued_at_date = *gmtime(&buffer->issued_at); if (ImGui::DatePicker("##issuedAt", issued_at_date)) { @@ -164,7 +66,6 @@ static void draw_expense_form(invoice* buffer, bool viewing_only = false) ImGui::SameLine(); ImGui::Text(localize("invoice.form.issuedat")); - // 5. Invoice expires at tm expires_at_date = *gmtime(&buffer->expires_at); if (ImGui::DatePicker("##expiresAt", expires_at_date)) { @@ -173,7 +74,6 @@ static void draw_expense_form(invoice* buffer, bool viewing_only = false) ImGui::SameLine(); ImGui::Text(localize("invoice.form.expiresat")); - // 6. Product/service delivered at tm delivered_at_date = *gmtime(&buffer->delivered_at); if (ImGui::DatePicker("##deliveredAt", delivered_at_date)) { @@ -184,31 +84,26 @@ static void draw_expense_form(invoice* buffer, bool viewing_only = false) ImGui::Separator(); - // 7. Supplier information ImGui::Text(localize("invoice.form.supplier")); - draw_contact_form_ex(&buffer->supplier, false, true, 0); + draw_contact_form_ex(&buffer->supplier, false, true); - // 8. (optional) shipping address. ImGui::Checkbox(localize("invoice.form.triangulation"), &buffer->is_triangulation); if (buffer->is_triangulation) { ImGui::Spacing(); ImGui::Text(localize("invoice.form.shippinginformation")); - draw_contact_form_ex(&buffer->addressee, 0,0,0); + draw_contact_form_ex(&buffer->addressee, 0,0); } ImGui::Separator(); - // 9. Project selection - draw_project_selector(buffer->project_id, project_list_buffer); + ImGui::FormProjectCombo(buffer->project_id); + ImGui::FormCostCenterCombo(buffer->cost_center_id); - // 10. Cost center selection - draw_costcenter_selector(buffer->cost_center_id, cost_center_list_buffer); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); 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(localize("invoice.form.add")))) @@ -218,17 +113,15 @@ static void draw_expense_form(invoice* buffer, bool viewing_only = false) } if (max_items_reached) ImGui::EndDisabled(); - // 12. Dropdown for invoice currency. ImGui::SameLine(); ImGui::Text("| %s: ", localize("invoice.form.currency")); ImGui::SameLine(); - if (draw_currency_selector(buffer->currency)) + if (ImGui::FormCurrencyCombo(buffer->currency)) { administration_invoice_set_currency(buffer, buffer->currency); } - // 13. Invoice items form - draw_invoice_items_form(buffer, false); + draw_invoice_items_form(buffer); if (viewing_only) ImGui::EndDisabled(); } diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp index c3c6b8c..f061e3f 100644 --- a/src/ui/ui_invoices.cpp +++ b/src/ui/ui_invoices.cpp @@ -33,19 +33,13 @@ static view_state current_view_state = view_state::LIST; static invoice active_invoice = {0}; static invoice selected_for_removal = {0}; -static tax_rate* tax_rate_list_buffer = 0; static billing_item* invoice_items_buffer = 0; -static project* project_list_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 draw_project_selector(char* project_id, project* buffer); +void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false); void ui_destroy_invoices() { - free(tax_rate_list_buffer); free(invoice_items_buffer); - free(project_list_buffer); } void ui_setup_invoices() @@ -53,129 +47,11 @@ void ui_setup_invoices() current_view_state = view_state::LIST; active_invoice = administration_invoice_create_empty(); - u32 tax_rate_count = administration_tax_rate_count(); - tax_rate_list_buffer = (tax_rate*) malloc(sizeof(tax_rate) * tax_rate_count); - u32 invoice_items_count = MAX_BILLING_ITEMS; invoice_items_buffer = (billing_item*)malloc(sizeof(billing_item) * invoice_items_count); - - u32 project_count = administration_project_count(); - project_list_buffer = (project*) malloc(sizeof(project) * project_count); -} - -void draw_tax_rate_selector(char* tax_rate_id, tax_rate* buffer, char* orig_country, char* dest_country) -{ - tax_rate* selected_tax_rate = NULL; - char* country_codes[2] = {orig_country, dest_country}; - u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, country_codes); - - // Select tax rate by given id. - if (strlen(tax_rate_id) > 0) - { - for (u32 i = 0; i < tax_rate_count; i++) - { - if (strcmp(buffer[i].id, tax_rate_id) == 0) - { - selected_tax_rate = &buffer[i]; - break; - } - } - } - - int selected_tax_rate_index = -1; - char rate_str_buf[40]; - rate_str_buf[0] = 0; - if (selected_tax_rate) - { - if (strcmp(selected_tax_rate->country_code, "00") == 0) { - char category_code_desc[MAX_LEN_LONG_DESC]; - snprintf(category_code_desc, MAX_LEN_LONG_DESC, "taxcategory.%s", selected_tax_rate->category_code); - snprintf(rate_str_buf, 40, "%s", localize(category_code_desc)); - } - else snprintf(rate_str_buf, 40, "%s/%.1f%%", selected_tax_rate->country_code, selected_tax_rate->rate); - } - - if (ImGui::BeginCombo("##Tax Bracket", rate_str_buf)) - { - for (u32 n = 0; n < tax_rate_count; n++) - { - bool is_selected = selected_tax_rate && strcmp(selected_tax_rate->id, buffer[n].id) == 0; - - if (strcmp(buffer[n].country_code, "00") == 0) { - char category_code_desc[MAX_LEN_LONG_DESC]; - snprintf(category_code_desc, MAX_LEN_LONG_DESC, "taxcategory.%s", buffer[n].category_code); - snprintf(rate_str_buf, 40, "%s", localize(category_code_desc)); - } - else snprintf(rate_str_buf, 40, "%s/%.1f%%", buffer[n].country_code, buffer[n].rate); - - if (ImGui::Selectable(rate_str_buf, is_selected)) { - selected_tax_rate_index = n; - } - } - ImGui::EndCombo(); - } - - if (selected_tax_rate_index != -1) { - strops_copy(tax_rate_id, buffer[selected_tax_rate_index].id, MAX_LEN_ID); - } } -bool draw_currency_selector(char* currency) -{ - int currentCurrency = 0; - bool result = false; - - // Top 15 most traded currencies + all EU official currencies - const char* currencies[] = { - // Top 15 - "EUR", "USD", "JPY", "GBP", "AUD", "CAD", "CHF", - "CNY", "HKD", "NZD", "SEK", "KRW", "SGD", "NOK", "MXN", - - // Additional EU currencies - "BGN", // Bulgarian Lev - "CZK", // Czech Koruna - "DKK", // Danish Krone - "HUF", // Hungarian Forint - "PLN", // Polish Zloty - "RON", // Romanian Leu - // "HRK", // Croatian Kuna (legacy, replaced by EUR in 2023) - }; - int currency_count = sizeof(currencies) / sizeof(char*); - - if (strlen(currency) > 0) - { - for (int i = 0; i < currency_count; i++) - { - if (strcmp(currencies[i], currency) == 0) - { - currentCurrency = i; - break; - } - } - } - - ImGui::SetNextItemWidth(100.0f); - if (ImGui::BeginCombo("##currency", currencies[currentCurrency])) - { - for (int n = 0; n < IM_ARRAYSIZE(currencies); n++) - { - bool isSelected = (currentCurrency == n); - if (ImGui::Selectable(currencies[n], isSelected)) - { - result = true; - strops_copy(currency, currencies[n], MAX_LEN_CURRENCY); - } - - if (isSelected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - - return result; -} - -void draw_invoice_items_form(invoice* invoice, bool is_outgoing) +void draw_invoice_items_form(invoice* invoice) { billing_item* buffer = invoice_items_buffer; u32 invoice_items = administration_billing_item_get_all_for_invoice(invoice, buffer); @@ -212,22 +88,7 @@ void draw_invoice_items_form(invoice* invoice, bool is_outgoing) ImGui::InputFloat("##amount", &item.amount, 0.0f, 0.0f, "%.0f"); ImGui::SameLine(); - // Toggle between X and % - { - const char* items[] = { "X", "%" }; - if (ImGui::BeginCombo("Mode", items[item.amount_is_percentage])) { - for (int n = 0; n < 2; n++) { - bool is_selected = (n == (int)item.amount_is_percentage); - if (ImGui::Selectable(items[n], is_selected)) { - item.amount_is_percentage = n; - } - if (is_selected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } - } + ImGui::FormToggleCombo(&item.amount_is_percentage, "X", "%"); ImGui::TableSetColumnIndex(2); ImGui::PushItemWidth(-1); @@ -243,31 +104,15 @@ void draw_invoice_items_form(invoice* invoice, bool is_outgoing) ImGui::InputFloat("##discount", &item.discount, 0.0f, 0.0f, "%.2f"); ImGui::SameLine(); - // Toggle between currency and % - { - const char* items[] = { item.currency, "%" }; - if (ImGui::BeginCombo("Mode##discountMode", items[item.discount_is_percentage])) { - for (int n = 0; n < 2; n++) { - bool is_selected = (n == (int)item.discount_is_percentage); - if (ImGui::Selectable(items[n], is_selected)) { - item.discount_is_percentage = n; - } - if (is_selected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } - } + ImGui::FormToggleCombo(&item.amount_is_percentage, item.currency, "%"); ImGui::TableSetColumnIndex(5); ImGui::Text("%.2f %s", item.net, item.currency); ImGui::TableSetColumnIndex(6); ImGui::PushItemWidth(-1); - draw_tax_rate_selector(item.tax_rate_id, tax_rate_list_buffer, - administration_company_info_get().address.country_code, - is_outgoing ? invoice->customer.address.country_code : invoice->supplier.address.country_code); + ImGui::FormTaxRateCombo(item.tax_rate_id, invoice->customer.address.country_code, invoice->supplier.address.country_code); + ImGui::PopItemWidth(); ImGui::TableSetColumnIndex(7); @@ -329,17 +174,9 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false) { ImGui::BeginDisabled(); - // 1. Identifier - //ImGui::SetNextItemWidth(widthAvailable*0.2f); - //ImGui::InputText(localize("contact.form.identifier"), buffer->id, IM_ARRAYSIZE(buffer->id)); - - // 2. Sequential number ImGui::Text("%s: %s", localize("invoice.form.invoicenumber"), buffer->sequential_number); - - // 3. Supplier (you) ImGui::Text("%s: %s", localize("invoice.form.supplier"), buffer->supplier.name); - // 4. Invoice issued at tm issued_at_date = *gmtime(&buffer->issued_at); if (ImGui::DatePicker("##issuedAt", issued_at_date)) { @@ -348,7 +185,6 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false) ImGui::SameLine(); ImGui::Text(localize("invoice.form.issuedat")); - // 5. Invoice expires at tm expires_at_date = *gmtime(&buffer->expires_at); if (ImGui::DatePicker("##expiresAt", expires_at_date)) { @@ -358,7 +194,6 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false) ImGui::Text(localize("invoice.form.expiresat")); if (!viewing_only) ImGui::EndDisabled(); - // 6. Product/service delivered at tm delivered_at_date = *gmtime(&buffer->delivered_at); if (ImGui::DatePicker("##deliveredAt", delivered_at_date)) { @@ -369,27 +204,23 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false) ImGui::Separator(); - // 7. Customer information ImGui::Text(localize("invoice.form.billinginformation")); - draw_contact_form_ex(&buffer->customer, false, true, 0); + draw_contact_form_ex(&buffer->customer, false, true); - // 8. (optional) shipping address. ImGui::Checkbox(localize("invoice.form.triangulation"), &buffer->is_triangulation); if (buffer->is_triangulation) { ImGui::Spacing(); ImGui::Text(localize("invoice.form.shippinginformation")); - draw_contact_form_ex(&buffer->addressee, 0,0,0); + draw_contact_form_ex(&buffer->addressee, 0,0); } ImGui::Separator(); - // 9. Project selection - draw_project_selector(buffer->project_id, project_list_buffer); + ImGui::FormProjectCombo(buffer->project_id); ImGui::Spacing(); ImGui::Spacing(); 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(localize("invoice.form.add")))) @@ -399,17 +230,15 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false) } if (max_items_reached) ImGui::EndDisabled(); - // 12. Dropdown for invoice currency. ImGui::SameLine(); ImGui::Text("| %s: ", localize("invoice.form.currency")); ImGui::SameLine(); - if (draw_currency_selector(buffer->currency)) + if (ImGui::FormCurrencyCombo(buffer->currency)) { administration_invoice_set_currency(buffer, buffer->currency); } - // 13. Invoice items form - draw_invoice_items_form(buffer, true); + draw_invoice_items_form(buffer); if (viewing_only) ImGui::EndDisabled(); } diff --git a/src/ui/ui_projects.cpp b/src/ui/ui_projects.cpp index dddf881..37cf5d8 100644 --- a/src/ui/ui_projects.cpp +++ b/src/ui/ui_projects.cpp @@ -35,8 +35,10 @@ void ui_setup_projects() static void draw_project_form() { + float widthAvailable = ImGui::GetContentRegionAvail().x; + bool viewing_only = (current_view_state == view_state::VIEW); static const char* selected_country = NULL; - + if (ImGui::Button(localize("form.back"))) { current_view_state = view_state::LIST; active_project = administration_project_create_empty(); @@ -44,20 +46,15 @@ static void draw_project_form() return; } ImGui::Spacing(); - - bool viewing_only = (current_view_state == view_state::VIEW); - ImGui::BeginDisabled(); - - float widthAvailable = ImGui::GetContentRegionAvail().x; + + a_err last_err = administration_project_is_valid(active_project); ImGui::SetNextItemWidth(widthAvailable*0.2f); ImGui::InputText(localize("project.form.identifier"), active_project.id, IM_ARRAYSIZE(active_project.id)); if (!viewing_only) ImGui::EndDisabled(); - - ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("project.form.description"), localize("project.form.description"), active_project.description, IM_ARRAYSIZE(active_project.description)); - ImGui::SameLine();ui_helper_draw_required_tag(); + + ImGui::FormInputTextWithErrorHint(localize("project.form.description"), &active_project, active_project.description, IM_ARRAYSIZE(active_project.description), last_err & A_ERR_MISSING_DESCRIPTION); if (viewing_only) ImGui::EndDisabled(); diff --git a/tests/peppol_write_tests.cpp b/tests/peppol_write_tests.cpp index 8e8c102..e77f128 100644 --- a/tests/peppol_write_tests.cpp +++ b/tests/peppol_write_tests.cpp @@ -1,20 +1,38 @@ +#include <malloc.h> + #include "zip.h" #include "xml.h" +char* validation_file = "libs\\PEPPOL-EN16931-UBL.sch"; +char* result_file = "build\\invoice_report.xml"; +char* extract_dir = "build\\extracted"; + static contact _create_nl_business() { contact pw = administration_contact_create_empty(); strops_copy(pw.name, "John Johnsson", sizeof(pw.name)); strops_copy(pw.address.address1, "Address 1", sizeof(pw.address.address1)); - strops_copy(pw.address.country_code, "FR", sizeof(pw.address.country_code)); - strops_copy(pw.address.city, "Paris", sizeof(pw.address.city)); - strops_copy(pw.address.postal, "12345", sizeof(pw.address.postal)); + strops_copy(pw.address.country_code, "NL", sizeof(pw.address.country_code)); + strops_copy(pw.address.city, "Amsterdam", sizeof(pw.address.city)); + strops_copy(pw.address.postal, "1234AA", sizeof(pw.address.postal)); strops_copy(pw.taxid, "T123", sizeof(pw.taxid)); strops_copy(pw.businessid, "B321", sizeof(pw.businessid)); pw.type = contact_type::CONTACT_BUSINESS; return pw; } +static contact _create_nl_consumer() +{ + contact pw = administration_contact_create_empty(); + strops_copy(pw.name, "Hans Klok", sizeof(pw.name)); + strops_copy(pw.address.address1, "Address 2", sizeof(pw.address.address1)); + strops_copy(pw.address.country_code, "NL", sizeof(pw.address.country_code)); + strops_copy(pw.address.city, "Amsterdam", sizeof(pw.address.city)); + strops_copy(pw.address.postal, "4321AA", sizeof(pw.address.postal)); + pw.type = contact_type::CONTACT_CONSUMER; + return pw; +} + static billing_item _create_bi1(invoice *inv) { billing_item item = administration_billing_item_create_empty(); @@ -38,10 +56,14 @@ static bool _test_peppol_file(char* id) { bool result = 1; - zip_extract(test_file_path, "build\\extracted", 0, 0); - system("java -jar libs\\schxslt-cli.jar -d build\\extracted\\I\\2.xml -s libs\\PEPPOL-EN16931-UBL.sch -o build\\invoice_report.xml > NUL 2>&1"); + zip_extract(test_file_path, extract_dir, 0, 0); - FILE* file = fopen("build\\invoice_report.xml", "r"); + char command[200]; + snprintf(command, 200, "java -jar libs\\schxslt-cli.jar -d %s\\%s.xml -s %s -o %s > NUL 2>&1", + extract_dir, id, validation_file, result_file); + system(command); + + FILE* file = fopen(result_file, "r"); struct xml_document* document = xml_open_document(file); struct xml_node* root = xml_document_root(document); @@ -56,7 +78,28 @@ static bool _test_peppol_file(char* id) if (strcmp(name, "svrl:failed-assert") == 0) { struct xml_node* text_node = xml_easy_child(node, (uint8_t *)"svrl:text", 0); char* content = (char*)xml_easy_content(text_node); - printf("FAILURE: %s\n", content); + + size_t num_attributes = xml_node_attributes(node); + for (int x = 0; x < num_attributes; x++) + { + struct xml_string* attr_name = xml_node_attribute_name(node, x); + size_t b_length = xml_string_length(attr_name); + uint8_t* b_buffer = (uint8_t*)alloca((b_length + 1) * sizeof(uint8_t)); + xml_string_copy(attr_name, b_buffer, b_length); + b_buffer[b_length] = 0; + + if (strcmp((char*)b_buffer, "location") == 0) { + struct xml_string* attr_content = xml_node_attribute_content(node, x); + + size_t a_length = xml_string_length(attr_content); + uint8_t* a_buffer = (uint8_t*)alloca((a_length + 1) * sizeof(uint8_t)); + xml_string_copy(attr_content, a_buffer, a_length); + a_buffer[a_length] = 0; + + printf("FAILURE: %s @ %s\n", content, (char*)a_buffer); + } + } + result = 0; } } @@ -67,10 +110,10 @@ static bool _test_peppol_file(char* id) ////////////////// // Netherlands ////////////////// -TEST _peppol_write_outgoing_nl2nl_b2b(void) +TEST _peppol_write_nl2nl_b2b(void) { administration_writer_create(); - administration_create_empty(test_file_path); + administration_create_default(test_file_path); invoice inv = administration_invoice_create_empty(); inv.supplier = _create_nl_business(); @@ -82,11 +125,32 @@ TEST _peppol_write_outgoing_nl2nl_b2b(void) inv.expires_at = inv.issued_at + 1000; administration_billing_item_add_to_invoice(&inv, _create_bi1(&inv)); - administration_invoice_add(&inv); + ASSERT_EQ(administration_invoice_add(&inv), A_ERR_SUCCESS); + + if (_test_peppol_file(inv.id)) { PASS(); } else { FAIL(); } +} + +TEST _peppol_write_nl2nl_b2c(void) +{ + administration_writer_create(); + administration_create_default(test_file_path); + + invoice inv = administration_invoice_create_empty(); + inv.supplier = _create_nl_business(); + inv.customer = _create_nl_consumer(); + inv.is_outgoing = 1; + inv.status = invoice_status::INVOICE_CONCEPT; + inv.issued_at = time(NULL); + inv.delivered_at = inv.issued_at; + inv.expires_at = inv.issued_at + 1000; + + administration_billing_item_add_to_invoice(&inv, _create_bi1(&inv)); + ASSERT_EQ(administration_invoice_add(&inv), A_ERR_SUCCESS); if (_test_peppol_file(inv.id)) { PASS(); } else { FAIL(); } } SUITE(peppol_write) { - RUN_TEST(_peppol_write_outgoing_nl2nl_b2b); + RUN_TEST(_peppol_write_nl2nl_b2b); + RUN_TEST(_peppol_write_nl2nl_b2c); }
\ No newline at end of file |
