summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/administration.cpp92
-rw-r--r--src/administration_reader.cpp246
-rw-r--r--src/administration_writer.cpp25
3 files changed, 340 insertions, 23 deletions
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, &copy, 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, &copy, 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 {