#define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include "ui.hpp" #include "strops.hpp" #include "administration_writer.hpp" #include "file_templates.hpp" mtx_t _save_file_mutex; bool administration_writer_create() { return mtx_init(&_save_file_mutex, mtx_plain) == thrd_success; } void administration_writer_destroy() { mtx_destroy(&_save_file_mutex); } static char* administration_writer_copy_template(const char* template_str, int* buf_size) { size_t template_size = strlen(template_str); size_t buf_length = template_size*2; // Ballpark file content size. char* file_content = (char*)malloc(buf_length); memset(file_content, 0, buf_length); memcpy(file_content, template_str, template_size); *buf_size = (int)buf_length; return file_content; } static bool administration_writer_entry_exists(char* entry) { bool entry_exists = false; struct zip_t *zip_read = zip_open(administration_file_path_get(), 0, 'r'); int i, n = (int)zip_entries_total(zip_read); for (i = 0; i < n; ++i) { zip_entry_openbyindex(zip_read, i); const char *entry_name = zip_entry_name(zip_read); if (strcmp(entry_name, entry) == 0) { entry_exists = true; break; } zip_entry_close(zip_read); } zip_close(zip_read); return entry_exists; } bool administration_writer_delete_entry(char* id) { bool result = 1; struct zip_t *zip_write = zip_open(administration_file_path_get(), 0, 'a'); if (!zip_write) zip_write = zip_open(administration_file_path_get(), 0, 'w'); char final_path[50]; snprintf(final_path, 50, "%s.xml", id); char* indices[1] = {final_path}; if (zip_entries_delete(zip_write, indices, 1) < 0) result = 0; zip_close(zip_write); return result; } static bool administration_writer_write_to_zip(char* entry_to_replace, char* orig_content, int final_length) { bool result = 1; bool entry_exists = administration_writer_entry_exists(entry_to_replace); struct zip_t *zip_write = zip_open(administration_file_path_get(), 0, 'a'); if (!zip_write) zip_write = zip_open(administration_file_path_get(), 0, 'w'); if (entry_exists) { char* indices[1] = {entry_to_replace}; zip_entries_delete(zip_write, indices, 1); zip_close(zip_write); zip_write = zip_open(administration_file_path_get(), 0, 'a'); } zip_entry_open(zip_write, entry_to_replace); if (zip_entry_write(zip_write, orig_content, final_length) < 0) result = 0; zip_entry_close(zip_write); zip_close(zip_write); return result; } ///////////////////////////// //// Invoices ///////////////////////////// static char* administration_writer_get_eas_id_for_contact(contact contact) { // https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/ char* country_code = contact.address.country_code; // Countries using tax identification numbers. if (strcmp(country_code, "AT") == 0) return contact.taxid; // Austria if (strcmp(country_code, "BE") == 0) return contact.taxid; // Belgium if (strcmp(country_code, "BG") == 0) return contact.taxid; // Bulgaria if (strcmp(country_code, "CY") == 0) return contact.taxid; // Cyprus if (strcmp(country_code, "CZ") == 0) return contact.taxid; // Czech Republic if (strcmp(country_code, "DE") == 0) return contact.taxid; // Germany if (strcmp(country_code, "EE") == 0) return contact.taxid; // Estonia if (strcmp(country_code, "FR") == 0) return contact.taxid; // France if (strcmp(country_code, "GR") == 0) return contact.taxid; // Greece if (strcmp(country_code, "HR") == 0) return contact.taxid; // Croatia if (strcmp(country_code, "HU") == 0) return contact.taxid; // Hungary if (strcmp(country_code, "IE") == 0) return contact.taxid; // Ireland if (strcmp(country_code, "LU") == 0) return contact.taxid; // Luxembourg if (strcmp(country_code, "LV") == 0) return contact.taxid; // Latvia if (strcmp(country_code, "MT") == 0) return contact.taxid; // Malta if (strcmp(country_code, "NL") == 0) return contact.taxid; // Netherlands if (strcmp(country_code, "PL") == 0) return contact.taxid; // Poland if (strcmp(country_code, "PT") == 0) return contact.taxid; // Portugal if (strcmp(country_code, "RO") == 0) return contact.taxid; // Romania if (strcmp(country_code, "SI") == 0) return contact.taxid; // Slovenia if (strcmp(country_code, "SK") == 0) return contact.taxid; // Slovakia if (strcmp(country_code, "ES") == 0) return contact.taxid; // Spain // Countries using business identification numbers. if (strcmp(country_code, "SE") == 0) return contact.businessid; // Sweden if (strcmp(country_code, "LT") == 0) return contact.businessid; // Lithuania if (strcmp(country_code, "IT") == 0) return contact.businessid; // Italy if (strcmp(country_code, "FI") == 0) return contact.businessid; // Finland if (strcmp(country_code, "DK") == 0) return contact.businessid; // Denmark return NULL; // Unknown country code } static char* administration_writer_get_eas_scheme_for_address(address addr) { // https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/ char* country_code = addr.country_code; if (strcmp(country_code, "AT") == 0) return "9914"; // Austria if (strcmp(country_code, "BE") == 0) return "9925"; // Belgium if (strcmp(country_code, "BG") == 0) return "9926"; // Bulgaria if (strcmp(country_code, "CY") == 0) return "9928"; // Cyprus if (strcmp(country_code, "CZ") == 0) return "9929"; // Czech Republic if (strcmp(country_code, "DE") == 0) return "9930"; // Germany if (strcmp(country_code, "DK") == 0) return "0096"; // Denmark if (strcmp(country_code, "EE") == 0) return "9931"; // Estonia if (strcmp(country_code, "FR") == 0) return "9957"; // France if (strcmp(country_code, "GR") == 0) return "9933"; // Greece if (strcmp(country_code, "HR") == 0) return "9934"; // Croatia if (strcmp(country_code, "HU") == 0) return "9910"; // Hungary if (strcmp(country_code, "IE") == 0) return "9935"; // Ireland if (strcmp(country_code, "FI") == 0) return "0212"; // Finland if (strcmp(country_code, "IT") == 0) return "0208"; // Italy if (strcmp(country_code, "LT") == 0) return "0200"; // Lithuania if (strcmp(country_code, "LU") == 0) return "9938"; // Luxembourg if (strcmp(country_code, "LV") == 0) return "9939"; // Latvia if (strcmp(country_code, "MT") == 0) return "9943"; // Malta if (strcmp(country_code, "NL") == 0) return "9944"; // Netherlands if (strcmp(country_code, "PL") == 0) return "9945"; // Poland if (strcmp(country_code, "PT") == 0) return "9946"; // Portugal if (strcmp(country_code, "RO") == 0) return "9947"; // Romania if (strcmp(country_code, "SE") == 0) return "0007"; // Sweden if (strcmp(country_code, "SI") == 0) return "9949"; // Slovenia if (strcmp(country_code, "SK") == 0) return "9950"; // Slovakia if (strcmp(country_code, "ES") == 0) return "9920"; // Spain return NULL; // Unknown country code } bool administration_writer_save_invoice_blocking(invoice inv) { bool result = 1; int buf_length = 15000; // Ballpark file content size. char* file_content = (char*)malloc(buf_length); memset(file_content, 0, buf_length); memcpy(file_content, peppol_invoice_template, strlen(peppol_invoice_template)); struct tm *tm_info = 0; char date_buffer[11]; // "YYYY-MM-DD" + null terminator strops_replace(file_content, buf_length, "{{INVOICE_ID}}", inv.sequential_number); strops_replace(file_content, buf_length, "{{CURRENCY}}", inv.currency); // Supplier data strops_replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_address(inv.supplier.address)); strops_replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_ID}}", administration_writer_get_eas_id_for_contact(inv.supplier)); strops_replace(file_content, buf_length, "{{SUPPLIER_NAME}}", inv.supplier.name); strops_replace(file_content, buf_length, "{{SUPPLIER_STREET}}", inv.supplier.address.address1); strops_replace(file_content, buf_length, "{{SUPPLIER_STREET2}}", inv.supplier.address.address2); strops_replace(file_content, buf_length, "{{SUPPLIER_CITY}}", inv.supplier.address.city); strops_replace(file_content, buf_length, "{{SUPPLIER_POSTAL}}", inv.supplier.address.postal); strops_replace(file_content, buf_length, "{{SUPPLIER_REGION}}", inv.supplier.address.region); strops_replace(file_content, buf_length, "{{SUPPLIER_COUNTRY}}", inv.supplier.address.country_code); strops_replace(file_content, buf_length, "{{SUPPLIER_VAT_ID}}", inv.supplier.taxid); // Customer data strops_replace(file_content, buf_length, "{{CUSTOMER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_address(inv.customer.address)); strops_replace(file_content, buf_length, "{{CUSTOMER_ENDPOINT_ID}}", administration_writer_get_eas_id_for_contact(inv.customer)); strops_replace(file_content, buf_length, "{{CUSTOMER_NAME}}", inv.customer.name); strops_replace(file_content, buf_length, "{{CUSTOMER_STREET}}", inv.customer.address.address1); strops_replace(file_content, buf_length, "{{CUSTOMER_STREET2}}", inv.customer.address.address2); strops_replace(file_content, buf_length, "{{CUSTOMER_CITY}}", inv.customer.address.city); strops_replace(file_content, buf_length, "{{CUSTOMER_POSTAL}}", inv.customer.address.postal); strops_replace(file_content, buf_length, "{{CUSTOMER_REGION}}", inv.customer.address.region); strops_replace(file_content, buf_length, "{{CUSTOMER_COUNTRY}}", inv.customer.address.country_code); strops_replace(file_content, buf_length, "{{CUSTOMER_VAT_ID}}", inv.customer.taxid); // 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); // Tax breakdown strops_replace_float(file_content, buf_length, "{{TOTAL_TAX_AMOUNT}}", inv.total, 2); { // Create tax subtotal list. country_tax_bracket* tax_bracket_buffer = (country_tax_bracket*)malloc(sizeof(country_tax_bracket)*administration_billing_item_count(&inv)); u32 tax_bracket_count = administration_invoice_get_tax_brackets(&inv, tax_bracket_buffer); //int tax_list_buf_length = 0; //char* tax_list_file_content = administration_writer_copy_template(peppol_invoice_tax_subtotal_template, &tax_list_buf_length); for (u32 i = 0; i < tax_bracket_count; i++) { int tax_entry_buf_length = 0; char* tax_entry_file_content = administration_writer_copy_template(peppol_invoice_tax_subtotal_template, &tax_entry_buf_length); tax_subtotal subtotal; administration_invoice_get_subtotal_for_tax_bracket(&inv, tax_bracket_buffer[i], &subtotal); strops_replace(tax_entry_file_content, tax_entry_buf_length, "{{CURRENCY}}", inv.currency); strops_replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAXABLE_AMOUNT}}", subtotal.net, 2); strops_replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAX_AMOUNT}}", subtotal.tax, 2); //strops_replace(tax_entry_file_content, tax_entry_buf_length, "{{TAX_CATEGORY}}", inv.currency); strops_replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAX_PERCENT}}", tax_bracket_buffer[i].rate, 2); printf("%s\n", tax_entry_file_content); } free(tax_bracket_buffer); } // Dates tm_info = localtime(&inv.issued_at); strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d", tm_info); strops_replace(file_content, buf_length, "{{ISSUE_DATE}}", date_buffer); //// Write to Disk. char final_path[50]; snprintf(final_path, 50, "%s.xml", inv.id); int final_length = (int)strlen(file_content); if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; free(file_content); return result; } static bool administration_writer_save_all_invoices_blocking() { bool result = 1; u32 num_invoices = administration_invoice_count(); u32 buffer_size = sizeof(invoice) * num_invoices; invoice* invoice_buffer = (invoice*)malloc(buffer_size); num_invoices = administration_invoice_get_all(invoice_buffer); for (u32 i = 0; i < num_invoices; i++) { invoice c = invoice_buffer[i]; if (!administration_writer_save_invoice_blocking(c)) result = 0; } free(invoice_buffer); return result; } ///////////////////////////// //// Projects ///////////////////////////// bool administration_writer_save_project_blocking(project project) { bool result = 1; int buf_length = 0; char* file_content = administration_writer_copy_template(project_save_template, &buf_length); strops_replace(file_content, buf_length, "{{PROJECT_ID}}", project.id); strops_replace(file_content, buf_length, "{{PROJECT_DESCRIPTION}}", project.description); strops_replace_int32(file_content, buf_length, "{{PROJECT_STATE}}", project.state); strops_replace_int64(file_content, buf_length, "{{PROJECT_STARTDATE}}", (long long)project.start_date); strops_replace_int64(file_content, buf_length, "{{PROJECT_ENDDATE}}", (long long)project.end_date); //// Write to Disk. char final_path[50]; snprintf(final_path, 50, "%s.xml", project.id); int final_length = (int)strlen(file_content); if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; free(file_content); return result; } static bool administration_writer_save_all_projects_blocking() { bool result = 1; u32 num_projects = administration_project_count(); u32 buffer_size = sizeof(project) * num_projects; project* project_buffer = (project*)malloc(buffer_size); num_projects = administration_project_get_all(project_buffer); for (u32 i = 0; i < num_projects; i++) { project c = project_buffer[i]; if (!administration_writer_save_project_blocking(c)) result = 0; } free(project_buffer); return result; } ///////////////////////////// //// Cost centers ///////////////////////////// bool administration_writer_save_cost_center_blocking(cost_center cost) { bool result = 1; int buf_length = 0; char* file_content = administration_writer_copy_template(costcenter_save_template, &buf_length); strops_replace(file_content, buf_length, "{{COSTCENTER_ID}}", cost.id); strops_replace(file_content, buf_length, "{{COSTCENTER_CODE}}", cost.code); strops_replace(file_content, buf_length, "{{COSTCENTER_DESCRIPTION}}", cost.description); //// Write to Disk. char final_path[50]; snprintf(final_path, 50, "%s.xml", cost.id); int final_length = (int)strlen(file_content); if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; free(file_content); return result; } static bool administration_writer_save_all_cost_centers_blocking() { bool result = 1; u32 num_costcenters = administration_cost_center_count(); u32 buffer_size = sizeof(cost_center) * num_costcenters; cost_center* costcenter_buffer = (cost_center*)malloc(buffer_size); num_costcenters = administration_cost_center_get_all(costcenter_buffer); for (u32 i = 0; i < num_costcenters; i++) { cost_center c = costcenter_buffer[i]; if (!administration_writer_save_cost_center_blocking(c)) result = 0; } free(costcenter_buffer); return result; } ///////////////////////////// //// Tax brackets ///////////////////////////// bool administration_writer_save_tax_bracket_blocking(country_tax_bracket bracket) { bool result = 1; int buf_length = 0; char* file_content = administration_writer_copy_template(taxbracket_save_template, &buf_length); strops_replace(file_content, buf_length, "{{TAXBRACKET_ID}}", bracket.id); strops_replace(file_content, buf_length, "{{TAXBRACKET_COUNTRY}}", bracket.country_code); strops_replace_float(file_content, buf_length, "{{TAXBRACKET_RATE}}", bracket.rate, 2); strops_replace(file_content, buf_length, "{{TAXBRACKET_DESCRIPTION}}", bracket.description); //// Write to Disk. char final_path[50]; snprintf(final_path, 50, "%s.xml", bracket.id); int final_length = (int)strlen(file_content); if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; free(file_content); return result; } static bool administration_writer_save_all_tax_brackets_blocking() { //// Get all data. u32 num_brackets = administration_tax_bracket_count(); u32 buffer_size = sizeof(country_tax_bracket) * num_brackets; country_tax_bracket* bracket_buffer = (country_tax_bracket*)malloc(buffer_size); num_brackets = administration_tax_bracket_get_all(bracket_buffer); bool result = 1; for (u32 i = 0; i < num_brackets; i++) { country_tax_bracket c = bracket_buffer[i]; if (!administration_writer_save_tax_bracket_blocking(c)) result = 0; } free(bracket_buffer); return result; } ///////////////////////////// //// Contacts ///////////////////////////// bool administration_writer_save_contact_blocking(contact c) { bool result = 1; int buf_length = 0; char* file_content = administration_writer_copy_template(contact_save_template, &buf_length); strops_replace(file_content, buf_length, "{{CONTACT_ID}}", c.id); strops_replace(file_content, buf_length, "{{CONTACT_NAME}}", c.name); strops_replace_int32(file_content, buf_length, "{{CONTACT_TYPE}}", c.type); strops_replace(file_content, buf_length, "{{CONTACT_TAXID}}", c.taxid); strops_replace(file_content, buf_length, "{{CONTACT_BUSINESSID}}", c.businessid); strops_replace(file_content, buf_length, "{{CONTACT_EMAIL}}", c.email); strops_replace(file_content, buf_length, "{{CONTACT_PHONENUMBER}}", c.phone_number); strops_replace(file_content, buf_length, "{{CONTACT_BANKACCOUNT}}", c.bank_account); strops_replace(file_content, buf_length, "{{CONTACT_ADDRESS1}}", c.address.address1); strops_replace(file_content, buf_length, "{{CONTACT_ADDRESS2}}", c.address.address2); strops_replace(file_content, buf_length, "{{CONTACT_COUNTRY}}", c.address.country_code); strops_replace(file_content, buf_length, "{{CONTACT_CITY}}", c.address.city); strops_replace(file_content, buf_length, "{{CONTACT_POSTAL}}", c.address.postal); strops_replace(file_content, buf_length, "{{CONTACT_REGION}}", c.address.region); char final_path[50]; snprintf(final_path, 50, "%s.xml", c.id); int final_length = (int)strlen(file_content); if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; free(file_content); return result; } static bool administration_writer_save_all_contacts_blocking() { //// Get all data. u32 num_contacts = administration_contact_count(); u32 buffer_size = sizeof(contact) * num_contacts; contact* contact_buffer = (contact*)malloc(buffer_size); num_contacts = administration_contact_get_all(contact_buffer); bool result = 1; // Save company info. if (!administration_writer_save_contact_blocking(administration_company_info_get())) result = 0; // Save contacts. for (u32 i = 0; i < num_contacts; i++) { contact c = ((contact*)contact_buffer)[i]; if (!administration_writer_save_contact_blocking(c)) result = 0; } free(contact_buffer); return result; } ///////////////////////////// //// Administration info ///////////////////////////// bool administration_writer_save_all_administration_info_blocking() { bool result = 1; int buf_length = 0; char* file_content = administration_writer_copy_template(administration_save_template, &buf_length); strops_replace_int32(file_content, buf_length, "{{NEXT_ID}}", administation_get()->next_id); strops_replace_int32(file_content, buf_length, "{{NEXT_SEQUENCE_NUMBER}}", administation_get()->next_sequence_number); strops_replace(file_content, buf_length, "{{PROGRAM_VERSION}}", administation_get()->program_version); //// Write to Disk. int final_length = (int)strlen(file_content); if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; else if (!administration_writer_write_to_zip(ADMIN_FILE_INFO, file_content, final_length)) result = 0; free(file_content); return result; } ///////////////////////////// //// Other ///////////////////////////// static int administration_writer_write_all_t(void *arg) { (void)arg; int result = 1; mtx_lock(&_save_file_mutex); if (!administration_writer_save_all_invoices_blocking()) result = 0; if (!administration_writer_save_all_projects_blocking()) result = 0; if (!administration_writer_save_all_administration_info_blocking()) result = 0; if (!administration_writer_save_all_tax_brackets_blocking()) result = 0; if (!administration_writer_save_all_cost_centers_blocking()) result = 0; if (!administration_writer_save_all_contacts_blocking()) result = 0; mtx_unlock(&_save_file_mutex); ui_set_status_loading(false); return result; } bool administration_writer_save_all_async() { ui_set_status_loading(true); thrd_t thr; if (thrd_create(&thr, administration_writer_write_all_t, 0) != thrd_success) { return false; } return true; }