diff options
| -rw-r--r-- | docs/CHANGES.rst | 10 | ||||
| -rw-r--r-- | include/administration.hpp | 5 | ||||
| -rw-r--r-- | include/administration_reader.hpp | 3 | ||||
| -rw-r--r-- | include/file_templates.hpp | 33 | ||||
| -rw-r--r-- | libs/xml.c/src/xml.c | 12 | ||||
| -rw-r--r-- | libs/xml.c/src/xml.h | 2 | ||||
| -rw-r--r-- | src/administration.cpp | 92 | ||||
| -rw-r--r-- | src/administration_reader.cpp | 246 | ||||
| -rw-r--r-- | src/administration_writer.cpp | 25 | ||||
| -rw-r--r-- | tests/administration_rw_tests.cpp | 119 | ||||
| -rw-r--r-- | tests/administration_validation_tests.cpp | 8 | ||||
| -rw-r--r-- | tests/main.cpp | 2 | ||||
| -rw-r--r-- | tests/peppol_write_tests.cpp | 123 | ||||
| -rw-r--r-- | tests/test_helper.cpp | 223 |
14 files changed, 717 insertions, 186 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst index 2bb8399..8d07a81 100644 --- a/docs/CHANGES.rst +++ b/docs/CHANGES.rst @@ -1,8 +1,16 @@ .. _changes: TODO: +- move xml functions out of administration reader into xml.c +- refactor _add functions to use _import functions - write tests that check error handling for corrupt files. (e.g. references to tax rates, project and cost center that failed to load) -- get rid of customer_id and supplier_id. Just check for existing contact entries when finishing invoice and create new contact if necessary. +- log load failures +- it is possible a referenced tax rate is loaded after an invoice is loaded. This means all invoices need to be recalculated after file load. (try to write a test for this). + +- get rid of customer_id and supplier_id. +This also means we can get rid of administration_invoice_set_refs which simplifies things alot. Contacts should just be a list of possible contacts +that the user can select from and create manually if they want. They should not be automatically created. + - project start and end date should be stored as YYYY-MM-DD - Send invoice by email - create invoice from PDF file diff --git a/include/administration.hpp b/include/administration.hpp index e2ff80d..130993f 100644 --- a/include/administration.hpp +++ b/include/administration.hpp @@ -270,7 +270,6 @@ typedef struct bool is_outgoing; // Outgoing or incomming invoice. payment_information payment_means; - // Used for forms, not stored on disk. Filled when retrieved. contact supplier; contact customer; } invoice; @@ -474,6 +473,7 @@ u32 administration_invoice_count(); u32 administration_invoice_get_incomming_count(); u32 administration_invoice_get_outgoing_count(); invoice administration_invoice_create_empty(); +a_err administration_invoice_import(invoice* invoice); a_err administration_invoice_add(invoice* invoice); a_err administration_invoice_update(invoice* invoice); a_err administration_invoice_remove(invoice* invoice); @@ -486,7 +486,7 @@ char* administration_invoice_get_status_string(invoice* invoice); u32 administration_invoice_get_partial_list_outgoing(u32 page_index, u32 page_size, invoice* buffer); u32 administration_invoice_get_partial_list_incomming(u32 page_index, u32 page_size, invoice* buffer); u32 administration_invoice_get_all(invoice* buffer); - +a_err administration_invoice_get_by_id(invoice* buffer, char* id); u32 administration_invoice_get_tax_rates(invoice* invoice, tax_rate* buffer); bool administration_invoice_get_subtotal_for_tax_rate(invoice* invoice, tax_rate rate, tax_subtotal* buffer); @@ -494,6 +494,7 @@ bool administration_invoice_get_subtotal_for_tax_rate(invoice* invoice, tax_r // ======================= u32 administration_billing_item_count(invoice* invoice); billing_item administration_billing_item_create_empty(); +a_err administration_billing_item_import_to_invoice(invoice* invoice, billing_item item); a_err administration_billing_item_add_to_invoice(invoice* invoice, billing_item item); a_err administration_billing_item_update_in_invoice(invoice* invoice, billing_item item); a_err administration_billing_item_remove_from_invoice(invoice* invoice, billing_item item); diff --git a/include/administration_reader.hpp b/include/administration_reader.hpp index cbd69ba..7765680 100644 --- a/include/administration_reader.hpp +++ b/include/administration_reader.hpp @@ -24,4 +24,5 @@ bool administration_reader_import_administration_info(char* buffer, size_t buffe bool administration_reader_import_tax_rate(char* buffer, size_t buffer_size); bool administration_reader_import_cost_center(char* buffer, size_t buffer_size); bool administration_reader_import_project(char* buffer, size_t buffer_size); -bool administration_reader_import_contact(char* buffer, size_t buffer_size);
\ No newline at end of file +bool administration_reader_import_contact(char* buffer, size_t buffer_size); +bool administration_reader_import_invoice(char* buffer, size_t buffer_size);
\ No newline at end of file diff --git a/include/file_templates.hpp b/include/file_templates.hpp index 9064abf..4ed3611 100644 --- a/include/file_templates.hpp +++ b/include/file_templates.hpp @@ -94,6 +94,12 @@ const char* peppol_invoice_line_template = " <cac:Item>\n" " <cbc:Name>{{ITEM_NAME}}</cbc:Name>\n" + +" <cac:AdditionalItemProperty>\n" +" <cbc:Name>Internal Tax Rate ID</cbc:Name>\n" +" <cbc:Value>{{LINE_TAX_ID}}</cbc:Value>\n" +" </cac:AdditionalItemProperty>\n" + " <cac:ClassifiedTaxCategory>\n" " <cbc:ID>{{LINE_TAX_CATEGORY}}</cbc:ID>\n" " <cbc:Percent>{{LINE_TAX_PERCENT}}</cbc:Percent>\n" @@ -123,24 +129,28 @@ const char *peppol_invoice_template = " <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>\n" " <cbc:DocumentCurrencyCode>{{CURRENCY}}</cbc:DocumentCurrencyCode>\n" "\n" +" <cac:DespatchDocumentReference>\n" +" <cbc:ID>{{INVOICE_STATUS}}</cbc:ID>\n" +" </cac:DespatchDocumentReference>\n" +"\n" " <cac:AdditionalDocumentReference>\n" " <cbc:ID>{{INVOICE_DOCUMENT}}</cbc:ID>\n" " </cac:AdditionalDocumentReference>\n" "\n" " <cac:OrderReference>\n" -" <cbc:ID>{{INVOICE_ID}}</cbc:ID>\n" +" <cbc:ID>{{INVOICE_SEQUENCE_ID}}</cbc:ID>\n" " </cac:OrderReference>\n" "\n" " <cac:ProjectReference>\n" " <cbc:ID>{{PROJECT_ID}}</cbc:ID>\n" " </cac:ProjectReference>\n" -" <cbc:AccountingCost>{{COST_CENTER_CODE}}</cbc:AccountingCost>\n" +" <cbc:AccountingCost>{{COST_CENTER_ID}}</cbc:AccountingCost>\n" "\n" " <cac:AccountingSupplierParty>\n" " <cac:Party>\n" " <cbc:EndpointID schemeID=\"{{SUPPLIER_ENDPOINT_SCHEME}}\">{{SUPPLIER_ENDPOINT_ID}}</cbc:EndpointID>\n" " <cac:PartyIdentification>\n" -" <cbc:ID schemeID=\"ZZZ\">{{SUPPLIER_ID}}</cbc:ID>\n" +" <cbc:ID schemeID=\"ZZZ\">{{SUPPLIER_BUSINESS_ID}}</cbc:ID>\n" " </cac:PartyIdentification>\n" " <cac:PartyName>\n" " <cbc:Name>{{SUPPLIER_NAME}}</cbc:Name>\n" @@ -167,7 +177,7 @@ const char *peppol_invoice_template = " </cac:PartyLegalEntity>\n" "\n" " <cac:Contact>\n" -" <cbc:Name>{{SUPPLIER_NAME}}</cbc:Name>\n" +" <cbc:Name>{{SUPPLIER_ID}}</cbc:Name>\n" " <cbc:Telephone>{{SUPPLIER_PHONE_NUMBER}}</cbc:Telephone>\n" " <cbc:ElectronicMail>{{SUPPLIER_EMAIL}}</cbc:ElectronicMail>\n" " </cac:Contact>\n" @@ -179,7 +189,7 @@ const char *peppol_invoice_template = " <cac:Party>\n" " <cbc:EndpointID schemeID=\"{{CUSTOMER_ENDPOINT_SCHEME}}\">{{CUSTOMER_ENDPOINT_ID}}</cbc:EndpointID>\n" " <cac:PartyIdentification>\n" -" <cbc:ID schemeID=\"ZZZ\">{{CUSTOMER_ID}}</cbc:ID>\n" +" <cbc:ID schemeID=\"ZZZ\">{{CUSTOMER_BUSINESS_ID}}</cbc:ID>\n" " </cac:PartyIdentification>\n" " <cac:PartyName>\n" " <cbc:Name>{{CUSTOMER_NAME}}</cbc:Name>\n" @@ -206,7 +216,7 @@ const char *peppol_invoice_template = " </cac:PartyLegalEntity>\n" "\n" " <cac:Contact>\n" -" <cbc:Name>{{CUSTOMER_NAME}}</cbc:Name>\n" +" <cbc:Name>{{CUSTOMER_ID}}</cbc:Name>\n" " <cbc:Telephone>{{CUSTOMER_PHONE_NUMBER}}</cbc:Telephone>\n" " <cbc:ElectronicMail>{{CUSTOMER_EMAIL}}</cbc:ElectronicMail>\n" " </cac:Contact>\n" @@ -238,14 +248,21 @@ const char *peppol_invoice_template = " <cac:PaymentMeans>\n" " <cbc:PaymentMeansCode>{{PAYMENT_TYPE}}</cbc:PaymentMeansCode>\n" " <cbc:PaymentID>{{INVOICE_ID}}</cbc:PaymentID>\n" + " <cac:PayeeFinancialAccount>\n" -" <cbc:ID>{{SUPPLIER_IBAN}}</cbc:ID>\n" +" <cbc:ID>{{RECIPIENT_IBAN}}</cbc:ID>\n" +" <cbc:Name>{{RECIPIENT_NAME}}</cbc:Name>\n" " <cac:FinancialInstitutionBranch>\n" " <cac:FinancialInstitution>\n" -" <cbc:ID>{{SUPPLIER_BIC}}</cbc:ID>\n" +" <cbc:ID>{{RECIPIENT_BIC}}</cbc:ID>\n" " </cac:FinancialInstitution>\n" " </cac:FinancialInstitutionBranch>\n" " </cac:PayeeFinancialAccount>\n" + +" <cac:PayerFinancialAccount>\n" +" <cbc:ID>{{SENDER_IBAN}}</cbc:ID>\n" +" </cac:PayerFinancialAccount>\n" + " </cac:PaymentMeans>\n" "\n" " <cac:TaxTotal>\n" diff --git a/libs/xml.c/src/xml.c b/libs/xml.c/src/xml.c index 9f8bf24..2eda83e 100644 --- a/libs/xml.c/src/xml.c +++ b/libs/xml.c/src/xml.c @@ -1019,15 +1019,13 @@ struct xml_string* xml_node_attribute_content(struct xml_node* node, size_t attr /** * [PUBLIC API] */ -struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child_name, ...) { + struct xml_node* xml_easy_vchild(struct xml_node* node, uint8_t const* child_name, va_list arguments) { /* Find children, one by one */ struct xml_node* current = node; - va_list arguments; - va_start(arguments, child_name); - + //va_start(arguments, child_name); /* Descent to current.child */ @@ -1080,6 +1078,12 @@ struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child_name return current; } +struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child_name, ...) { + + va_list arguments; + va_start(arguments, child_name); + return xml_easy_vchild(node, child_name, arguments); +} /** diff --git a/libs/xml.c/src/xml.h b/libs/xml.c/src/xml.h index 688a4be..b5ce81f 100644 --- a/libs/xml.c/src/xml.h +++ b/libs/xml.c/src/xml.h @@ -154,7 +154,7 @@ struct xml_string* xml_node_attribute_content(struct xml_node* node, size_t attr * @warning Last argument must be 0 */ struct xml_node* xml_easy_child(struct xml_node* node, uint8_t const* child, ...); - +struct xml_node* xml_easy_vchild(struct xml_node* node, uint8_t const* child_name, va_list arguments); /** diff --git a/src/administration.cpp b/src/administration.cpp index 690fcd9..a21c70f 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -1012,7 +1012,7 @@ a_err administration_contact_get_by_id(contact* buffer, char* id) contact c = *(contact *)list_iterator_next(&g_administration.contacts); if (strcmp(c.id, id) == 0) { - list_iterator_stop(&g_administration.projects); + list_iterator_stop(&g_administration.contacts); *buffer = c; result = A_ERR_SUCCESS; } @@ -1579,8 +1579,10 @@ invoice administration_invoice_create_empty() 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(); + result.issued_at -= (result.issued_at % 86400); + + result.delivered_at = result.issued_at; + result.expires_at = result.issued_at + administration_get_default_invoice_expire_duration(); list_init(&result.billing_items); // @leak strops_copy(result.currency, administration_get_default_currency_for_country(g_administration.company_info.address.country_code), MAX_LEN_CURRENCY); return result; @@ -1767,12 +1769,20 @@ a_err administration_invoice_update(invoice* inv) return A_ERR_NOT_FOUND; } -a_err administration_invoice_add(invoice* inv) +a_err administration_invoice_import(invoice* inv) { a_err result = administration_invoice_is_valid(inv); if (result != A_ERR_SUCCESS) return result; - administration_invoice_set_refs(inv); + if (!inv->is_triangulation) + { + memcpy(&inv->addressee, &inv->customer, sizeof(contact)); + } + + inv->issued_at -= (inv->issued_at % 86400); + inv->delivered_at -= (inv->delivered_at % 86400); + inv->expires_at -= (inv->expires_at % 86400); + inv->is_outgoing = strcmp(inv->supplier.id, MY_COMPANY_ID) == 0; invoice copy = administration_invoice_create_copy(inv); // Create copy to make copy of billing item list. invoice* new_inv = (invoice*)malloc(sizeof(invoice)); @@ -1780,11 +1790,36 @@ a_err administration_invoice_add(invoice* inv) memcpy(new_inv, ©, sizeof(invoice)); - 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)); - strops_copy(new_inv->payment_means.payer_bank_account, inv->supplier.bank_account, sizeof(new_inv->payment_means.payer_bank_account)); + if (!list_append(&g_administration.invoices, new_inv)) { + return A_ERR_GENERIC; + } + + return A_ERR_SUCCESS; +} + +a_err administration_invoice_add(invoice* inv) +{ + a_err result = administration_invoice_is_valid(inv); + if (result != A_ERR_SUCCESS) return result; + + administration_invoice_set_refs(inv); + + inv->issued_at -= (inv->issued_at % 86400); + inv->delivered_at -= (inv->delivered_at % 86400); + inv->expires_at -= (inv->expires_at % 86400); + inv->is_outgoing = strcmp(inv->supplier.id, MY_COMPANY_ID) == 0; + + inv->payment_means.payment_method = PAYMENT_METHOD_STANDING_AGREEMENT; + strops_copy(inv->payment_means.payee_bank_account, inv->customer.bank_account, sizeof(inv->payment_means.payee_bank_account)); + strops_copy(inv->payment_means.payee_account_name, inv->customer.name, sizeof(inv->payment_means.payee_account_name)); + strops_copy(inv->payment_means.service_provider_id, "", sizeof(inv->payment_means.service_provider_id)); + strops_copy(inv->payment_means.payer_bank_account, inv->supplier.bank_account, sizeof(inv->payment_means.payer_bank_account)); + + invoice copy = administration_invoice_create_copy(inv); // Create copy to make copy of billing item list. + invoice* new_inv = (invoice*)malloc(sizeof(invoice)); + if (!new_inv) return A_ERR_GENERIC; + + memcpy(new_inv, ©, sizeof(invoice)); if (!list_append(&g_administration.invoices, new_inv)) { return A_ERR_GENERIC; @@ -1838,6 +1873,24 @@ u32 administration_invoice_get_outgoing_count() return g_administration.invoice_count; } +a_err administration_invoice_get_by_id(invoice* buffer, char* id) +{ + a_err result = A_ERR_NOT_FOUND; + list_iterator_start(&g_administration.invoices); + while (list_iterator_hasnext(&g_administration.invoices)) { + invoice c = *(invoice *)list_iterator_next(&g_administration.invoices); + + if (strcmp(c.id, id) == 0) { + list_iterator_stop(&g_administration.invoices); + *buffer = c; + result = A_ERR_SUCCESS; + } + } + list_iterator_stop(&g_administration.invoices); + + return result; +} + u32 administration_invoice_get_all(invoice* buffer) { assert(buffer); @@ -2065,6 +2118,25 @@ a_err administration_billing_item_is_valid(billing_item item) return result; } +a_err administration_billing_item_import_to_invoice(invoice* invoice, billing_item item) +{ + if (administration_billing_item_count(invoice) >= MAX_BILLING_ITEMS) return A_ERR_MAX_ITEMS_REACHED; + + billing_item* tb = (billing_item*)malloc(sizeof(billing_item)); + if (!tb) return A_ERR_GENERIC; + + memcpy(tb, &item, sizeof(billing_item)); + + administration_recalculate_billing_item_totals(tb); + administration_recalculate_invoice_totals(invoice); + + if (!list_append(&invoice->billing_items, tb)) { + return A_ERR_GENERIC; + } + + return A_ERR_SUCCESS; +} + a_err administration_billing_item_add_to_invoice(invoice* invoice, billing_item item) { if (administration_billing_item_count(invoice) >= MAX_BILLING_ITEMS) return A_ERR_MAX_ITEMS_REACHED; diff --git a/src/administration_reader.cpp b/src/administration_reader.cpp index 8c6bf89..3ea6316 100644 --- a/src/administration_reader.cpp +++ b/src/administration_reader.cpp @@ -14,11 +14,13 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <malloc.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <zip.h> #include <xml.h> +#include <time.h> #include "log.hpp" #include "strops.hpp" @@ -104,6 +106,10 @@ bool administration_reader_open_existing(char* file_path) { administration_reader_import_contact(buffer, (size_t)size); } + else if (strops_prefix("I/", name)) + { + administration_reader_import_invoice(buffer, (size_t)size); + } free(buffer); } @@ -136,6 +142,64 @@ static s64 _get_xml_s64(xml_node* root, char* child_name) static s32 _get_xml_s32(xml_node* root, char* child_name) { struct xml_node* node = xml_easy_child(root, (uint8_t *)child_name, 0); + if (!node) return 0; + + char xml_content[512]; + memset(xml_content, 0, 512); + struct xml_string* str = xml_node_content(node); + xml_string_copy(str, (uint8_t *)xml_content, xml_string_length(str)); + + char *endptr; + long val = strtol(xml_content, &endptr, 10); + + s32 num = (int32_t) val; + return num; +} + +static xml_node* _get_xml_node_x(xml_node* root, char* child_name, ...) +{ + va_list arguments; + va_start(arguments, child_name); + + struct xml_node* node = xml_easy_vchild(root, (const uint8_t *)child_name, arguments); + return node; +} + +static char* _get_xml_str_attribute(xml_node* root, char* buffer, size_t bufsize, char* attribute_name, char* child_name, ...) +{ + va_list arguments; + va_start(arguments, child_name); + + struct xml_node* node = xml_easy_vchild(root, (const uint8_t *)child_name, arguments); + if (!node) return 0; + + 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, attribute_name) == 0) { + struct xml_string* attr_content = xml_node_attribute_content(node, x); + + xml_string_copy(attr_content, (uint8_t *)buffer, bufsize); + buffer[bufsize-1] = 0; + return buffer; + } + } + return 0; +} + +static s32 _get_xml_s32_x(xml_node* root, char* child_name, ...) +{ + va_list arguments; + va_start(arguments, child_name); + + struct xml_node* node = xml_easy_vchild(root, (const uint8_t *)child_name, arguments); + if (!node) return 0; char xml_content[512]; memset(xml_content, 0, 512); @@ -152,6 +216,25 @@ static s32 _get_xml_s32(xml_node* root, char* child_name) static float _get_xml_float(xml_node* root, char* child_name) { struct xml_node* node = xml_easy_child(root, (uint8_t *)child_name, 0); + if (!node) return 0; + + char xml_content[512]; + memset(xml_content, 0, 512); + struct xml_string* str = xml_node_content(node); + xml_string_copy(str, (uint8_t *)xml_content, xml_string_length(str)); + + char *endptr; + float val = strtof(xml_content, &endptr); + return val; +} + +static float _get_xml_float_x(xml_node* root, char* child_name, ...) +{ + va_list arguments; + va_start(arguments, child_name); + + struct xml_node* node = xml_easy_vchild(root, (const uint8_t *)child_name, arguments); + if (!node) return 0; char xml_content[512]; memset(xml_content, 0, 512); @@ -166,6 +249,7 @@ static float _get_xml_float(xml_node* root, char* child_name) static char* _get_xml_str(xml_node* root, char* buffer, size_t bufsize, char* child_name) { struct xml_node* node = xml_easy_child(root, (uint8_t *)child_name, 0); + if (!node) return 0; memset(buffer, 0, bufsize); struct xml_string* str = xml_node_content(node); @@ -174,6 +258,168 @@ static char* _get_xml_str(xml_node* root, char* buffer, size_t bufsize, char* ch return buffer; } +static char* _get_xml_str_x(xml_node* root, char* buffer, size_t bufsize, char* child_name, ...) +{ + va_list arguments; + va_start(arguments, child_name); + + struct xml_node* node = xml_easy_vchild(root, (const uint8_t *)child_name, arguments); + if (!node) return 0; + + memset(buffer, 0, bufsize); + struct xml_string* str = xml_node_content(node); + xml_string_copy(str, (uint8_t *)buffer, xml_string_length(str)); + + return buffer; +} + +static time_t _get_xml_date_x(xml_node* root, char* child_name, ...) +{ + va_list arguments; + va_start(arguments, child_name); + + struct xml_node* node = xml_easy_vchild(root, (const uint8_t *)child_name, arguments); + if (!node) return 0; + + char date_buffer[11]; + struct xml_string* str = xml_node_content(node); + xml_string_copy(str, (uint8_t *)date_buffer, xml_string_length(str)); + + struct tm tm_info = {0}; + int year, month, day; + if (sscanf(date_buffer, "%d-%d-%d", &year, &month, &day) != 3) { + return (time_t)-1; // parse failed + } + + tm_info.tm_year = year - 1900; // struct tm expects years since 1900 + tm_info.tm_mon = month - 1; // struct tm months are 0–11 + tm_info.tm_mday = day; + tm_info.tm_hour = 0; + tm_info.tm_min = 0; + tm_info.tm_sec = 0; + + return mktime(&tm_info) + 86400; // Hack +} + +bool administration_reader_import_invoice(char* buffer, size_t buffer_size) +{ + STOPWATCH_START; + + xml_document* document = xml_parse_document((uint8_t *)buffer, buffer_size); + if (!document) return false; + + struct xml_node* root = xml_document_root(document); + + invoice data = administration_invoice_create_empty(); + _get_xml_str(root, data.id, MAX_LEN_ID, "cbc:ID"); + _get_xml_str_x(root, data.sequential_number, MAX_LEN_ID, "cac:OrderReference", "cbc:ID", 0); + _get_xml_str(root, data.currency, MAX_LEN_CURRENCY, "cbc:DocumentCurrencyCode"); + data.status = (invoice_status)_get_xml_s32_x(root, "cac:DespatchDocumentReference", "cbc:ID", 0); + + // Dates + data.issued_at = _get_xml_date_x(root, "cbc:IssueDate", 0); + data.expires_at = _get_xml_date_x(root, "cbc:DueDate", 0); + data.delivered_at = _get_xml_date_x(root, "cac:Delivery", "cbc:ActualDeliveryDate", 0); + + // References + _get_xml_str_x(root, data.document, MAX_LEN_PATH, "cac:AdditionalDocumentReference", "cbc:ID", 0); + _get_xml_str_x(root, data.project_id, MAX_LEN_ID, "cac:ProjectReference", "cbc:ID", 0); + _get_xml_str(root, data.cost_center_id, MAX_LEN_ID, "cac:AccountingCost"); + + // Payment means + data.payment_means.payment_method = (payment_method)_get_xml_s32_x(root, "cac:PaymentMeans", "cbc:PaymentMeansCode", 0); + _get_xml_str_x(root, data.payment_means.payee_bank_account, MAX_LEN_BANK, "cac:PaymentMeans", "cac:PayeeFinancialAccount", "cbc:ID", 0); + _get_xml_str_x(root, data.payment_means.payee_account_name, MAX_LEN_LONG_DESC, "cac:PaymentMeans", "cac:PayeeFinancialAccount", "cbc:Name", 0); + _get_xml_str_x(root, data.payment_means.service_provider_id, MAX_LEN_ID, "cac:PaymentMeans", "cac:PayeeFinancialAccount", "cac:FinancialInstitutionBranch", "cac:FinancialInstitution", "cbc:ID", 0); + _get_xml_str_x(root, data.payment_means.payer_bank_account, MAX_LEN_BANK, "cac:PaymentMeans", "cac:PayerFinancialAccount", "cbc:ID", 0); + + // Totals + data.tax = _get_xml_float_x(root, "cac:TaxTotal", "cbc:TaxAmount", 0); + data.total = _get_xml_float_x(root, "cac:LegalMonetaryTotal", "cbc:TaxInclusiveAmount", 0); + data.net = _get_xml_float_x(root, "cac:LegalMonetaryTotal", "cbc:TaxExclusiveAmount", 0); + data.allowance = data.net - _get_xml_float_x(root, "cac:LegalMonetaryTotal", "cbc:LineExtensionAmount", 0); + + // Supplier + _get_xml_str_x(root, data.supplier.id, MAX_LEN_ID, "cac:AccountingSupplierParty", "cac:Party", "cac:Contact", "cbc:Name", 0); + strops_copy(data.supplier_id, data.supplier.id, MAX_LEN_ID); + strops_copy(data.supplier.bank_account, data.payment_means.payee_bank_account, MAX_LEN_BANK); + _get_xml_str_x(root, data.supplier.name, MAX_LEN_LONG_DESC, "cac:AccountingSupplierParty", "cac:Party", "cac:PartyName", "cbc:Name", 0); + _get_xml_str_x(root, data.supplier.address.address1, MAX_LEN_ADDRESS, "cac:AccountingSupplierParty", "cac:Party", "cac:PostalAddress", "cbc:StreetName", 0); + _get_xml_str_x(root, data.supplier.address.address2, MAX_LEN_ADDRESS, "cac:AccountingSupplierParty", "cac:Party", "cac:PostalAddress", "cbc:AdditionalStreetName", 0); + _get_xml_str_x(root, data.supplier.address.city, MAX_LEN_ADDRESS, "cac:AccountingSupplierParty", "cac:Party", "cac:PostalAddress", "cbc:CityName", 0); + _get_xml_str_x(root, data.supplier.address.postal, MAX_LEN_ADDRESS, "cac:AccountingSupplierParty", "cac:Party", "cac:PostalAddress", "cbc:PostalZone", 0); + _get_xml_str_x(root, data.supplier.address.region, MAX_LEN_ADDRESS, "cac:AccountingSupplierParty", "cac:Party", "cac:PostalAddress", "cbc:CountrySubentity", 0); + _get_xml_str_x(root, data.supplier.address.country_code, MAX_LEN_COUNTRY_CODE, "cac:AccountingSupplierParty", "cac:Party", "cac:PostalAddress", "cac:Country", "cbc:IdentificationCode", 0); + _get_xml_str_x(root, data.supplier.taxid, MAX_LEN_TAXID, "cac:AccountingSupplierParty", "cac:Party", "cac:PartyTaxScheme", "cbc:CompanyID", 0); + _get_xml_str_x(root, data.supplier.businessid, MAX_LEN_BUSINESSID, "cac:AccountingSupplierParty", "cac:Party", "cac:PartyIdentification", "cbc:ID", 0); + _get_xml_str_x(root, data.supplier.phone_number, MAX_LEN_PHONE, "cac:AccountingSupplierParty", "cac:Party", "cac:Contact", "cbc:Telephone", 0); + _get_xml_str_x(root, data.supplier.email, MAX_LEN_EMAIL, "cac:AccountingSupplierParty", "cac:Party", "cac:Contact", "cbc:ElectronicMail", 0); + + // Customer + _get_xml_str_x(root, data.customer.id, MAX_LEN_ID, "cac:AccountingCustomerParty", "cac:Party", "cac:Contact", "cbc:Name", 0); + strops_copy(data.customer_id, data.customer.id, MAX_LEN_ID); + strops_copy(data.customer.bank_account, data.payment_means.payer_bank_account, MAX_LEN_BANK); + _get_xml_str_x(root, data.customer.name, MAX_LEN_LONG_DESC, "cac:AccountingCustomerParty", "cac:Party", "cac:PartyName", "cbc:Name", 0); + _get_xml_str_x(root, data.customer.address.address1, MAX_LEN_ADDRESS, "cac:AccountingCustomerParty", "cac:Party", "cac:PostalAddress", "cbc:StreetName", 0); + _get_xml_str_x(root, data.customer.address.address2, MAX_LEN_ADDRESS, "cac:AccountingCustomerParty", "cac:Party", "cac:PostalAddress", "cbc:AdditionalStreetName", 0); + _get_xml_str_x(root, data.customer.address.city, MAX_LEN_ADDRESS, "cac:AccountingCustomerParty", "cac:Party", "cac:PostalAddress", "cbc:CityName", 0); + _get_xml_str_x(root, data.customer.address.postal, MAX_LEN_ADDRESS, "cac:AccountingCustomerParty", "cac:Party", "cac:PostalAddress", "cbc:PostalZone", 0); + _get_xml_str_x(root, data.customer.address.region, MAX_LEN_ADDRESS, "cac:AccountingCustomerParty", "cac:Party", "cac:PostalAddress", "cbc:CountrySubentity", 0); + _get_xml_str_x(root, data.customer.address.country_code, MAX_LEN_COUNTRY_CODE, "cac:AccountingCustomerParty", "cac:Party", "cac:PostalAddress", "cac:Country", "cbc:IdentificationCode", 0); + _get_xml_str_x(root, data.customer.taxid, MAX_LEN_TAXID, "cac:AccountingCustomerParty", "cac:Party", "cac:PartyTaxScheme", "cbc:CompanyID", 0); + _get_xml_str_x(root, data.customer.businessid, MAX_LEN_BUSINESSID, "cac:AccountingCustomerParty", "cac:Party", "cac:PartyIdentification", "cbc:ID", 0); + _get_xml_str_x(root, data.customer.phone_number, MAX_LEN_PHONE, "cac:AccountingCustomerParty", "cac:Party", "cac:Contact", "cbc:Telephone", 0); + _get_xml_str_x(root, data.customer.email, MAX_LEN_EMAIL, "cac:AccountingCustomerParty", "cac:Party", "cac:Contact", "cbc:ElectronicMail", 0); + + // Addressee + _get_xml_str_x(root, data.addressee.name, MAX_LEN_LONG_DESC, "cac:Delivery", "cac:DeliveryParty", "cac:PartyName", "cbc:Name", 0); + _get_xml_str_x(root, data.addressee.address.address1, MAX_LEN_ADDRESS, "cac:Delivery", "cac:DeliveryLocation", "cac:Address", "cbc:StreetName", 0); + _get_xml_str_x(root, data.addressee.address.address2, MAX_LEN_ADDRESS, "cac:Delivery", "cac:DeliveryLocation", "cac:Address", "cbc:AdditionalStreetName", 0); + _get_xml_str_x(root, data.addressee.address.city, MAX_LEN_ADDRESS, "cac:Delivery", "cac:DeliveryLocation", "cac:Address", "cbc:CityName", 0); + _get_xml_str_x(root, data.addressee.address.postal, MAX_LEN_ADDRESS, "cac:Delivery", "cac:DeliveryLocation", "cac:Address", "cbc:PostalZone", 0); + _get_xml_str_x(root, data.addressee.address.region, MAX_LEN_ADDRESS, "cac:Delivery", "cac:DeliveryLocation", "cac:Address", "cbc:CountrySubentity", 0); + _get_xml_str_x(root, data.addressee.address.country_code, MAX_LEN_COUNTRY_CODE, "cac:Delivery", "cac:DeliveryLocation", "cac:Address", "cac:Country", "cbc:IdentificationCode", 0); + + size_t child_count = xml_node_children(root); + for (size_t x = 0; x < child_count; x++) + { + xml_node* child = xml_node_child(root, x); + + char* child_name = (char*)xml_easy_name(child); + if (strcmp(child_name, "cac:InvoiceLine") == 0) + { + + billing_item bi = {0}; + _get_xml_str_x(child, bi.id, MAX_LEN_ID, "cbc:ID", 0); + _get_xml_str_x(child, bi.tax_rate_id, MAX_LEN_ID, "cac:Item", "cac:AdditionalItemProperty", "cbc:Value", 0); + bi.amount = _get_xml_float_x(child, "cbc:InvoicedQuantity", 0); + bi.net_per_item = _get_xml_float_x(child, "cac:Price", "cbc:PriceAmount", 0); + bi.net = _get_xml_float_x(child, "cbc:LineExtensionAmount", 0); + bi.discount = _get_xml_float_x(child, "cac:AllowanceCharge", "cbc:Amount", 0); + _get_xml_str_x(child, bi.description, MAX_LEN_LONG_DESC, "cac:Item", "cbc:Name", 0); + + char percentage_buffer[5] = {0}; + _get_xml_str_attribute(child, percentage_buffer, 5, "unitCode", "cbc:InvoicedQuantity", 0); + bi.amount_is_percentage = strcmp(percentage_buffer, "%") == 0; + + _get_xml_str_attribute(child, bi.currency, 5, "currencyID", "cbc:LineExtensionAmount", 0); + bi.discount_is_percentage = _get_xml_node_x(child, "cac:AllowanceCharge", "cbc:MultiplierFactorNumeric", 0) != 0; + if (bi.discount_is_percentage) { + bi.discount = _get_xml_float_x(child, "cac:AllowanceCharge", "cbc:MultiplierFactorNumeric", 0); + } + + administration_billing_item_import_to_invoice(&data, bi); + } + + free(child_name); + } + + bool result = administration_invoice_import(&data); + log_add("Loaded invoice '%s' in %.3fms.", data.sequential_number, STOPWATCH_TIME); + + return result; +} + bool administration_reader_import_contact(char* buffer, size_t buffer_size) { STOPWATCH_START; diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp index e02d476..75f5daf 100644 --- a/src/administration_writer.cpp +++ b/src/administration_writer.cpp @@ -298,13 +298,8 @@ bool administration_writer_save_invoice_blocking(invoice inv) struct tm *tm_info = 0; char date_buffer[11]; // "YYYY-MM-DD" + null terminator - // properties not stored from invoice: - // - id (can be retrieved from filename) - // - invoice allowance (can be recalculated from billing items) - // properties not stored from supplier/customer/addressee: // - type - // - bank account // These can all be retrieved from existing contacts. // properties not stored from billing item: @@ -312,11 +307,13 @@ bool administration_writer_save_invoice_blocking(invoice inv) // - tax (can be recalculated) // - total (can be recalculated) - strops_replace(file_content, buf_length, "{{INVOICE_ID}}", inv.sequential_number); + strops_replace(file_content, buf_length, "{{INVOICE_ID}}", inv.id); + strops_replace(file_content, buf_length, "{{INVOICE_SEQUENCE_ID}}", inv.sequential_number); strops_replace(file_content, buf_length, "{{CURRENCY}}", inv.currency); strops_replace(file_content, buf_length, "{{PROJECT_ID}}", inv.project_id); - strops_replace(file_content, buf_length, "{{COST_CENTER_CODE}}", inv.cost_center_id); + strops_replace(file_content, buf_length, "{{COST_CENTER_ID}}", inv.cost_center_id); strops_replace(file_content, buf_length, "{{INVOICE_DOCUMENT}}", inv.document); + strops_replace_int32(file_content, buf_length, "{{INVOICE_STATUS}}", (s32)inv.status); // Supplier data strops_replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_contact(inv.supplier)); @@ -366,9 +363,11 @@ bool administration_writer_save_invoice_blocking(invoice inv) // Payment means strops_replace_int32(file_content, buf_length, "{{PAYMENT_TYPE}}", inv.payment_means.payment_method); - strops_replace(file_content, buf_length, "{{SUPPLIER_IBAN}}", inv.payment_means.payee_bank_account); - strops_replace(file_content, buf_length, "{{SUPPLIER_BIC}}", inv.payment_means.service_provider_id); - + strops_replace(file_content, buf_length, "{{RECIPIENT_IBAN}}", inv.payment_means.payee_bank_account); + strops_replace(file_content, buf_length, "{{RECIPIENT_NAME}}", inv.payment_means.payee_account_name); + strops_replace(file_content, buf_length, "{{RECIPIENT_BIC}}", inv.payment_means.service_provider_id); + strops_replace(file_content, buf_length, "{{SENDER_IBAN}}", inv.payment_means.payer_bank_account); + // Tax breakdown strops_replace_float(file_content, buf_length, "{{TOTAL_TAX_AMOUNT}}", inv.tax, 2); { // Create tax subtotal list. @@ -431,9 +430,10 @@ bool administration_writer_save_invoice_blocking(invoice inv) billing_item bi = billing_item_buffer[i]; tax_rate rate; administration_tax_rate_get_by_id(&rate, bi.tax_rate_id); - + strops_replace(billing_item_file_content, billing_item_buf_length, "{{CURRENCY}}", bi.currency); strops_replace(billing_item_file_content, billing_item_buf_length, "{{LINE_ID}}", bi.id); + strops_replace(billing_item_file_content, billing_item_buf_length, "{{LINE_TAX_ID}}", bi.tax_rate_id); strops_replace(billing_item_file_content, billing_item_buf_length, "{{ITEM_NAME}}", bi.description); strops_replace(billing_item_file_content, billing_item_buf_length, "{{LINE_TAX_CATEGORY}}", rate.category_code); strops_replace_float(billing_item_file_content, billing_item_buf_length, "{{LINE_TAX_PERCENT}}", rate.rate, 2); @@ -441,10 +441,9 @@ 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); if (bi.discount_is_percentage) { - 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_TOTAL_PERCENTAGE}}", bi.discount, 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 { diff --git a/tests/administration_rw_tests.cpp b/tests/administration_rw_tests.cpp index ba5507e..b09fd9e 100644 --- a/tests/administration_rw_tests.cpp +++ b/tests/administration_rw_tests.cpp @@ -1,16 +1,3 @@ -#define _CRT_SECURE_NO_WARNINGS - -#include <stdio.h> -#include <stdlib.h> - -#include "greatest.h" -#include "timer.h" - -#include "strops.hpp" -#include "administration.hpp" -#include "administration_reader.hpp" -#include "administration_writer.hpp" - #ifdef _WIN32 #include <io.h> // for _unlink #define unlink _unlink @@ -18,8 +5,6 @@ #include <unistd.h> // for unlink #endif -char* test_file_path = "build\\test.openbook"; - static void setup_cb(void *data) { (void)data; unlink(test_file_path); @@ -174,6 +159,109 @@ TEST _administration_rw_info(void) PASS(); } +TEST _administration_rw_invoice(void) +{ + u32 count; + invoice inv; + invoice invr; + + administration_writer_create(); + + administration_create_default(test_file_path); + { + contact pw = administration_contact_create_empty(); + strops_copy(pw.name, "Piet Pinda", sizeof(pw.name)); + strops_copy(pw.address.address1, "Address 1", 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, "1234AA", sizeof(pw.address.postal)); + strops_copy(pw.taxid, "T123", sizeof(pw.taxid)); + strops_copy(pw.businessid, "B321", sizeof(pw.businessid)); + strops_copy(pw.email, "test@test.com", sizeof(pw.email)); + pw.type = contact_type::CONTACT_BUSINESS; + + inv = administration_invoice_create_empty(); + inv.supplier = pw; + inv.customer = pw; + 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_bi3(&inv)); + administration_billing_item_add_to_invoice(&inv, _create_bi4(&inv)); + administration_billing_item_add_to_invoice(&inv, _create_bi5(&inv)); + administration_billing_item_add_to_invoice(&inv, _create_bi6(&inv)); + + count = administration_invoice_count(); + ASSERT_EQ(administration_invoice_add(&inv), A_ERR_SUCCESS); + ASSERT_EQ(count+1, administration_invoice_count()); + } + + administration_reader_open_existing(test_file_path); + { + ASSERT_EQ(count+1, administration_invoice_count()); + ASSERT_EQ(A_ERR_SUCCESS, administration_invoice_get_by_id(&invr, inv.id)); + + ASSERT_STR_EQ(invr.id, inv.id); + ASSERT_STR_EQ(invr.sequential_number, inv.sequential_number); + ASSERT_STR_EQ(invr.customer_id, inv.customer_id); + ASSERT_STR_EQ(invr.supplier_id, inv.supplier_id); + + ASSERT_EQ(invr.issued_at, inv.issued_at); + ASSERT_EQ(invr.expires_at, inv.expires_at); + ASSERT_EQ(invr.delivered_at, inv.delivered_at); + ASSERT_STR_EQ(invr.document, inv.document); + ASSERT_STR_EQ(invr.project_id, inv.project_id); + ASSERT_STR_EQ(invr.cost_center_id, inv.cost_center_id); + + ASSERT_EQ(administration_billing_item_count(&inv), administration_billing_item_count(&invr)); + + int num_items = administration_billing_item_count(&inv); + for (int i = 0; i < num_items; i++) + { + billing_item* b1 = (billing_item*)list_get_at(&inv.billing_items, i); + billing_item* b2 = (billing_item*)list_get_at(&invr.billing_items, i); + + ASSERT_STR_EQ(b1->id, b2->id); + ASSERT_EQ(b1->amount, b2->amount); + ASSERT_EQ(b1->amount_is_percentage, b2->amount_is_percentage); + ASSERT_STR_EQ(b1->description, b2->description); + ASSERT_EQ(b1->net_per_item, b2->net_per_item); + ASSERT_EQ(b1->discount, b2->discount); + ASSERT_EQ(b1->discount_is_percentage, b2->discount_is_percentage); + ASSERT_EQ(b1->allowance, b2->allowance); + ASSERT_EQ(b1->net, b2->net); + ASSERT_STR_EQ(b1->currency, b2->currency); + ASSERT_STR_EQ(b1->tax_rate_id, b2->tax_rate_id); + ASSERT_EQ(b1->tax, b2->tax); + ASSERT_EQ(b1->total, b2->total); + } + + ASSERT_EQ(invr.orig_total, inv.orig_total); + ASSERT_EQ(invr.orig_tax, inv.orig_tax); + ASSERT_EQ(invr.orig_net, inv.orig_net); + ASSERT_EQ(invr.orig_allowance, inv.orig_allowance); + + ASSERT_EQ(invr.total, inv.total); + ASSERT_EQ(invr.tax, inv.tax); + ASSERT_EQ(invr.net, inv.net); + ASSERT_EQ(invr.allowance, inv.allowance); + + ASSERT_STR_EQ(invr.currency, inv.currency); + ASSERT_EQ(invr.is_triangulation, inv.is_triangulation); + ASSERT_EQ(invr.status, inv.status); + ASSERT_EQ(invr.is_outgoing, inv.is_outgoing); + ASSERT_MEM_EQ(&invr.payment_means, &inv.payment_means, sizeof(payment_information)); + ASSERT_MEM_EQ(&invr.supplier, &inv.supplier, sizeof(contact)); + ASSERT_MEM_EQ(&invr.customer, &inv.customer, sizeof(contact)); + ASSERT_MEM_EQ(&invr.addressee, &inv.addressee, sizeof(contact)); + } + + PASS(); +} + SUITE(administration_rw) { SET_SETUP(setup_cb, NULL); SET_TEARDOWN(teardown_cb, NULL); @@ -183,4 +271,5 @@ SUITE(administration_rw) { RUN_TEST(_administration_rw_project); RUN_TEST(_administration_rw_contact); RUN_TEST(_administration_rw_info); + RUN_TEST(_administration_rw_invoice); }
\ No newline at end of file diff --git a/tests/administration_validation_tests.cpp b/tests/administration_validation_tests.cpp index f307d2d..f406efd 100644 --- a/tests/administration_validation_tests.cpp +++ b/tests/administration_validation_tests.cpp @@ -1,11 +1,3 @@ -#include <stdio.h> -#include <stdlib.h> - -#include "greatest.h" -#include "timer.h" - -#include "strops.hpp" -#include "administration.hpp" // Project ////////////////// diff --git a/tests/main.cpp b/tests/main.cpp index 234c92d..358ca2e 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,3 +1,5 @@ +#include "test_helper.cpp" + #include "administration_rw_tests.cpp" #include "administration_validation_tests.cpp" #include "peppol_write_tests.cpp" diff --git a/tests/peppol_write_tests.cpp b/tests/peppol_write_tests.cpp index c00b53b..fb74ed7 100644 --- a/tests/peppol_write_tests.cpp +++ b/tests/peppol_write_tests.cpp @@ -1,126 +1,3 @@ -#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, "Piet Pinda", sizeof(pw.name)); - strops_copy(pw.address.address1, "Address 1", 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, "1234AA", sizeof(pw.address.postal)); - strops_copy(pw.taxid, "T123", sizeof(pw.taxid)); - strops_copy(pw.businessid, "B321", sizeof(pw.businessid)); - strops_copy(pw.email, "test@test.com", sizeof(pw.email)); - pw.type = contact_type::CONTACT_BUSINESS; - return pw; -} - -static contact _create_nl_consumer() -{ - contact pw = _create_nl_business(); - pw.type = contact_type::CONTACT_CONSUMER; - return pw; -} - -static billing_item _create_bi1(invoice *inv) -{ - billing_item item = administration_billing_item_create_empty(); - item.amount = 1; - item.amount_is_percentage = 0; - strops_copy(item.description, "Shampoo", MAX_LEN_LONG_DESC); - item.net_per_item = 10.00f; - item.discount = 2.0f; - item.discount_is_percentage = 0; - - tax_rate buffer[20]; - char* _country_codes[2] = {inv->supplier.address.country_code, inv->customer.address.country_code}; - u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, _country_codes); - tax_rate rand_rate = buffer[tax_rate_count-1]; - strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); - - return item; -} - -static billing_item _create_bi2(invoice *inv) -{ - billing_item item = administration_billing_item_create_empty(); - item.amount = 2; - item.amount_is_percentage = 0; - strops_copy(item.description, "Soap", MAX_LEN_LONG_DESC); - item.net_per_item = 5.00f; - item.discount = 5.0f; - item.discount_is_percentage = 1; - - tax_rate buffer[20]; - char* _country_codes[2] = {inv->supplier.address.country_code, inv->customer.address.country_code}; - u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, _country_codes); - tax_rate rand_rate = buffer[tax_rate_count-1]; - strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); - - return item; -} - -static bool _test_peppol_file(char* id) -{ - bool result = 1; - - zip_extract(test_file_path, extract_dir, 0, 0); - - 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); - - size_t num_children = xml_node_children(root); - for (int i = 0; i < num_children; i++) - { - struct xml_node* node = xml_node_child(root, i); - - char* name = (char*)xml_easy_name(node); - - 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); - - 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; - } - } - - return result; -} ////////////////// // Netherlands diff --git a/tests/test_helper.cpp b/tests/test_helper.cpp new file mode 100644 index 0000000..aedaccc --- /dev/null +++ b/tests/test_helper.cpp @@ -0,0 +1,223 @@ +#define _CRT_SECURE_NO_WARNINGS + +#include <stdio.h> +#include <stdlib.h> +#include <malloc.h> + +#include "zip.h" +#include "xml.h" +#include "greatest.h" +#include "timer.h" + +#include "strops.hpp" +#include "administration.hpp" +#include "administration_reader.hpp" +#include "administration_writer.hpp" + +char* test_file_path = "build\\test.openbook"; +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, "Dutch Company BV", sizeof(pw.name)); + strops_copy(pw.address.address1, "Address 1", 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.region, "Noord-Holland", sizeof(pw.address.region)); + 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)); + strops_copy(pw.email, "test@test.com", sizeof(pw.email)); + strops_copy(pw.phone_number, "+311234567", sizeof(pw.phone_number)); + strops_copy(pw.bank_account, "IBAN123456789", sizeof(pw.bank_account)); + pw.type = contact_type::CONTACT_BUSINESS; + return pw; +} + +static contact _create_nl_consumer() +{ + contact pw = administration_contact_create_empty(); + strops_copy(pw.name, "Piet Pinda", 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, "Maastricht", sizeof(pw.address.city)); + strops_copy(pw.address.region, "Limburg", sizeof(pw.address.region)); + strops_copy(pw.address.postal, "4321AA", sizeof(pw.address.postal)); + strops_copy(pw.email, "test@test.com", sizeof(pw.email)); + strops_copy(pw.phone_number, "+317654321", sizeof(pw.phone_number)); + pw.type = contact_type::CONTACT_CONSUMER; + return pw; +} + + +static billing_item _create_bi1(invoice *inv) +{ + billing_item item = administration_billing_item_create_empty(); + item.amount = 1; + item.amount_is_percentage = 0; + strops_copy(item.description, "Shampoo", MAX_LEN_LONG_DESC); + item.net_per_item = 10.00f; + item.discount = 2.0f; + item.discount_is_percentage = 0; + + tax_rate buffer[20]; + char* _country_codes[2] = {inv->supplier.address.country_code, inv->customer.address.country_code}; + u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, _country_codes); + tax_rate rand_rate = buffer[tax_rate_count-1]; + strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); + + return item; +} + +static billing_item _create_bi2(invoice *inv) +{ + billing_item item = administration_billing_item_create_empty(); + item.amount = 2; + item.amount_is_percentage = 0; + strops_copy(item.description, "Soap", MAX_LEN_LONG_DESC); + item.net_per_item = 5.00f; + item.discount = 5.0f; + item.discount_is_percentage = 1; + + tax_rate buffer[20]; + char* _country_codes[2] = {inv->supplier.address.country_code, inv->customer.address.country_code}; + u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, _country_codes); + tax_rate rand_rate = buffer[tax_rate_count-1]; + strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); + + return item; +} + +static billing_item _create_bi3(invoice *inv) +{ + billing_item item = administration_billing_item_create_empty(); + item.amount = 1; + item.amount_is_percentage = 0; + strops_copy(item.description, "Guacamole", MAX_LEN_LONG_DESC); + item.net_per_item = 10.00f; + item.discount = 2.0f; + item.discount_is_percentage = 0; + + tax_rate buffer[20]; + char* _country_codes[2] = {inv->supplier.address.country_code, inv->customer.address.country_code}; + u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, _country_codes); + tax_rate rand_rate = buffer[tax_rate_count-1]; + strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); + + return item; +} + +static billing_item _create_bi4(invoice *inv) +{ + billing_item item = administration_billing_item_create_empty(); + item.amount = 3; + item.amount_is_percentage = 0; + strops_copy(item.description, "Bananas", MAX_LEN_LONG_DESC); + item.net_per_item = 4.00f; + item.discount = 0.0f; + item.discount_is_percentage = 0; + + tax_rate buffer[20]; + char* _country_codes[2] = {inv->supplier.address.country_code, inv->customer.address.country_code}; + u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, _country_codes); + tax_rate rand_rate = buffer[tax_rate_count-1]; + strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); + + return item; +} + +static billing_item _create_bi5(invoice *inv) +{ + billing_item item = administration_billing_item_create_empty(); + item.amount = 5; + item.amount_is_percentage = 0; + strops_copy(item.description, "Apple", MAX_LEN_LONG_DESC); + item.net_per_item = 1.00f; + item.discount = 5.0f; + item.discount_is_percentage = 1; + + tax_rate buffer[20]; + char* _country_codes[2] = {inv->supplier.address.country_code, inv->customer.address.country_code}; + u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, _country_codes); + tax_rate rand_rate = buffer[tax_rate_count-1]; + strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); + + return item; +} + +static billing_item _create_bi6(invoice *inv) +{ + billing_item item = administration_billing_item_create_empty(); + item.amount = 10; + item.amount_is_percentage = 1; + strops_copy(item.description, "Tip", MAX_LEN_LONG_DESC); + item.net_per_item = 50.00f; + item.discount = 0.0f; + item.discount_is_percentage = 0; + + tax_rate buffer[20]; + char* _country_codes[2] = {inv->supplier.address.country_code, inv->customer.address.country_code}; + u32 tax_rate_count = administration_tax_rate_get_by_country(buffer, 2, _country_codes); + tax_rate rand_rate = buffer[tax_rate_count-1]; + strops_copy(item.tax_rate_id, rand_rate.id, MAX_LEN_ID); + + return item; +} + +static bool _test_peppol_file(char* id) +{ + bool result = 1; + + zip_extract(test_file_path, extract_dir, 0, 0); + + 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); + + size_t num_children = xml_node_children(root); + for (int i = 0; i < num_children; i++) + { + struct xml_node* node = xml_node_child(root, i); + + char* name = (char*)xml_easy_name(node); + + 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); + + 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; + } + } + + return result; +}
\ No newline at end of file |
