summaryrefslogtreecommitdiff
path: root/src/administration_reader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/administration_reader.cpp')
-rw-r--r--src/administration_reader.cpp246
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;