diff options
Diffstat (limited to 'src/administration_reader.cpp')
| -rw-r--r-- | src/administration_reader.cpp | 246 |
1 files changed, 246 insertions, 0 deletions
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; |
