#include #include #include #include #include #include "strops.hpp" #include "administration.hpp" administration g_administration; static s32 administration_create_id() { return g_administration.next_id; } #define ADD_BRACKET(_country, _rate, _description)\ {\ country_tax_bracket* tb = (country_tax_bracket*)malloc(sizeof(country_tax_bracket));\ snprintf(tb->id, sizeof(tb->id), "T/%d", administration_create_id());\ memcpy(tb->country_code, _country, sizeof(tb->country_code));\ tb->rate = _rate;\ memcpy(tb->description, _description, sizeof(tb->description));\ list_append(&g_administration.tax_brackets, tb);\ g_administration.next_id++;\ } static int compare_tax_countries(const void *a, const void *b) { country_tax_bracket *objA = (country_tax_bracket *)a; country_tax_bracket *objB = (country_tax_bracket *)b; return strcmp(objA->country_code, objB->country_code); } static void administration_create_default_tax_brackets() { // General brackets shared between countries. ADD_BRACKET("00", 0.0f, "tax.reverse_charge"); ADD_BRACKET("00", 0.0f, "tax.exempt"); // Austria ADD_BRACKET("AT", 20.0f, "tax.standard"); ADD_BRACKET("AT", 10.0f, "tax.reduced"); ADD_BRACKET("AT", 13.0f, "tax.reduced"); // Belgium ADD_BRACKET("BE", 21.0f, "tax.standard"); ADD_BRACKET("BE", 6.0f, "tax.reduced"); ADD_BRACKET("BE", 12.0f, "tax.reduced"); // Bulgaria ADD_BRACKET("BG", 20.0f, "tax.standard"); ADD_BRACKET("BG", 9.0f, "tax.reduced"); // Cyprus ADD_BRACKET("CY", 19.0f, "tax.standard"); ADD_BRACKET("CY", 5.0f, "tax.reduced"); ADD_BRACKET("CY", 9.0f, "tax.reduced"); // Czechia ADD_BRACKET("CZ", 21.0f, "tax.standard"); ADD_BRACKET("CZ", 12.0f, "tax.reduced"); // Croatia ADD_BRACKET("HR", 25.0f, "tax.standard"); ADD_BRACKET("HR", 5.0f, "tax.reduced"); ADD_BRACKET("HR", 13.0f, "tax.reduced"); // Denmark ADD_BRACKET("DK", 25.0f, "tax.standard"); // Estonia ADD_BRACKET("EE", 22.0f, "tax.standard"); ADD_BRACKET("EE", 9.0f, "tax.reduced"); // Finland ADD_BRACKET("FI", 25.5f, "tax.standard"); ADD_BRACKET("FI", 10.0f, "tax.reduced"); ADD_BRACKET("FI", 14.0f, "tax.reduced"); // France ADD_BRACKET("FR", 20.0f, "tax.standard"); ADD_BRACKET("FR", 5.5f, "tax.reduced"); ADD_BRACKET("FR", 10.0f, "tax.reduced"); ADD_BRACKET("FR", 2.1f, "tax.superReduced"); // Germany ADD_BRACKET("DE", 19.0f, "tax.standard"); ADD_BRACKET("DE", 7.0f, "tax.reduced"); // Greece ADD_BRACKET("GR", 24.0f, "tax.standard"); ADD_BRACKET("GR", 6.0f, "tax.reduced"); ADD_BRACKET("GR", 13.0f, "tax.reduced"); // Hungary ADD_BRACKET("HU", 27.0f, "tax.standard"); ADD_BRACKET("HU", 5.0f, "tax.reduced"); ADD_BRACKET("HU", 18.0f, "tax.reduced"); // Ireland ADD_BRACKET("IE", 23.0f, "tax.standard"); ADD_BRACKET("IE", 9.0f, "tax.reduced"); ADD_BRACKET("IE", 13.5f, "tax.reduced"); ADD_BRACKET("IE", 4.8f, "tax.superReduced"); // Italy ADD_BRACKET("IT", 22.0f, "tax.standard"); ADD_BRACKET("IT", 5.0f, "tax.reduced"); ADD_BRACKET("IT", 10.0f, "tax.reduced"); ADD_BRACKET("IT", 4.0f, "tax.superReduced"); // Latvia ADD_BRACKET("LV", 21.0f, "tax.standard"); ADD_BRACKET("LV", 5.0f, "tax.reduced"); ADD_BRACKET("LV", 12.0f, "tax.reduced"); // Lithuania ADD_BRACKET("LT", 21.0f, "tax.standard"); ADD_BRACKET("LT", 5.0f, "tax.reduced"); ADD_BRACKET("LT", 9.0f, "tax.reduced"); // Luxembourg ADD_BRACKET("LU", 17.0f, "tax.standard"); ADD_BRACKET("LU", 8.0f, "tax.reduced"); ADD_BRACKET("LU", 14.0f, "tax.reduced"); ADD_BRACKET("LU", 3.0f, "tax.superReduced"); // Malta ADD_BRACKET("MT", 18.0f, "tax.standard"); ADD_BRACKET("MT", 5.0f, "tax.reduced"); ADD_BRACKET("MT", 7.0f, "tax.reduced"); // Netherlands ADD_BRACKET("NL", 21.0f, "tax.standard"); ADD_BRACKET("NL", 9.0f, "tax.reduced"); // Poland ADD_BRACKET("PL", 23.0f, "tax.standard"); ADD_BRACKET("PL", 5.0f, "tax.reduced"); ADD_BRACKET("PL", 8.0f, "tax.reduced"); // Portugal ADD_BRACKET("PT", 23.0f, "tax.standard"); ADD_BRACKET("PT", 6.0f, "tax.reduced"); ADD_BRACKET("PT", 13.0f, "tax.reduced"); // Romania ADD_BRACKET("RO", 19.0f, "tax.standard"); ADD_BRACKET("RO", 5.0f, "tax.reduced"); ADD_BRACKET("RO", 9.0f, "tax.reduced"); // Slovakia ADD_BRACKET("SK", 23.0f, "tax.standard"); ADD_BRACKET("SK", 5.0f, "tax.reduced"); ADD_BRACKET("SK", 19.0f, "tax.reduced"); // Slovenia ADD_BRACKET("SI", 22.0f, "tax.standard"); ADD_BRACKET("SI", 5.0f, "tax.reduced"); ADD_BRACKET("SI", 9.5f, "tax.reduced"); // Spain ADD_BRACKET("ES", 21.0f, "tax.standard"); ADD_BRACKET("ES", 10.0f, "tax.reduced"); ADD_BRACKET("ES", 4.0f, "tax.superReduced"); // Sweden ADD_BRACKET("SE", 25.0f, "tax.standard"); ADD_BRACKET("SE", 6.0f, "tax.reduced"); ADD_BRACKET("SE", 12.0f, "tax.reduced"); list_attributes_comparator(&g_administration.tax_brackets, compare_tax_countries); list_sort(&g_administration.tax_brackets, -1); } static void administration_create_default_cost_centers() { #define ADD_COSTCENTER(_description, _code)\ {\ cost_center* tb = (cost_center*)malloc(sizeof(cost_center));\ snprintf(tb->id, sizeof(tb->id), "E/%d", administration_create_id());\ memcpy(tb->description, _description, sizeof(tb->description));\ memcpy(tb->code, _code, sizeof(tb->code));\ list_append(&g_administration.cost_centers, tb);\ g_administration.next_id++;\ } ADD_COSTCENTER("costcenter.general_expenses", "GENE"); ADD_COSTCENTER("costcenter.administration_general_management", "ADMN"); ADD_COSTCENTER("costcenter.finance_accounting", "FINC"); ADD_COSTCENTER("costcenter.information_technology", "INFO"); ADD_COSTCENTER("costcenter.sales_marketing", "SALE"); ADD_COSTCENTER("costcenter.operations_production", "OPER"); ADD_COSTCENTER("costcenter.supply_chain_logistics", "SUPP"); ADD_COSTCENTER("costcenter.research_development", "RDEV"); ADD_COSTCENTER("costcenter.facilities_maintenance", "FACL"); ADD_COSTCENTER("costcenter.customer_service_support", "CUST"); ADD_COSTCENTER("costcenter.other_specialized", "OTHR"); } static void administration_create_debug_data() { #define ADD_CONSUMER(_name, _addr1, _addr2, _cc)\ {contact _c = administration_create_empty_contact();\ strops_copy(_c.name, _name, sizeof(_c.name));\ strops_copy(_c.address.address1, _addr1, sizeof(_c.address.address1));\ strops_copy(_c.address.address2, _addr2, sizeof(_c.address.address2));\ strops_copy(_c.address.country_code, _cc, sizeof(_c.address.country_code));\ _c.type = contact_type::CONTACT_CONSUMER;\ administration_create_contact(_c);}; #define ADD_BUSINESS(_name, _addr1, _addr2, _cc, _tc, _bc)\ {contact _c = administration_create_empty_contact();\ strops_copy(_c.name, _name, sizeof(_c.name));\ strops_copy(_c.address.address1, _addr1, sizeof(_c.address.address1));\ strops_copy(_c.address.address2, _addr2, sizeof(_c.address.address2));\ strops_copy(_c.address.country_code, _cc, sizeof(_c.address.country_code));\ strops_copy(_c.taxid, _tc, sizeof(_c.taxid));\ strops_copy(_c.businessid, _bc, sizeof(_c.businessid));\ _c.type = contact_type::CONTACT_BUSINESS;\ administration_create_contact(_c);}; #define ADD_PROJECT(_name)\ {project _c = administration_create_empty_project();\ strops_copy(_c.description, _name, sizeof(_c.description));\ administration_create_project(_c);}; ADD_CONSUMER("Emma Müller", "Hauptstraße 12", "10115 Berlin", "DE"); ADD_CONSUMER("Luca Rossi", "Via Roma 45", "00184 Roma", "IT"); ADD_CONSUMER("Sofia Garcia", "Calle Mayor 7", "28013 Madrid", "ES"); ADD_CONSUMER("Jean Dupont", "10 Rue de la Paix", "75002 Paris", "FR"); ADD_CONSUMER("Anna Nowak", "ul. Kwiatowa 3", "00-001 Warszawa", "PL"); ADD_CONSUMER("Mikkel Jensen", "Østergade 8", "8000 Aarhus", "DK"); ADD_CONSUMER("Maria Svensson", "Kungsgatan 15", "111 22 Stockholm", "SE"); ADD_CONSUMER("Péter Kovács", "Fő utca 25", "1051 Budapest", "HU"); ADD_CONSUMER("Lucas Silva", "Rua Augusta 100", "1250-001 Lisboa", "PT"); ADD_CONSUMER("Isabelle Lefevre", "5 Place Stanislas", "54000 Nancy", "FR"); ADD_BUSINESS("Schmidt & Co GmbH", "Friedrichstraße 45", "10117 Berlin", "DE", "DE123456789", "HRB123456"); ADD_BUSINESS("Bianchi Srl", "Corso Venezia 12", "20121 Milano", "IT", "IT987654321", "MI1234567"); ADD_BUSINESS("Fernández y Asociados", "Gran Vía 20", "28013 Madrid", "ES", "ES456789123", "CIFB123456"); ADD_BUSINESS("Martin & Partners", "12 Avenue Victor Hugo", "75016 Paris", "FR", "FR321654987", "SIRET123456"); ADD_BUSINESS("Zielińska Consulting", "ul. Marszałkowska 10", "00-590 Warszawa", "PL", "PL789123456", "REGON123456"); ADD_BUSINESS("Sørensen ApS", "Strøget 3", "1460 København", "DK", "DK654321789", "CVR12345678"); ADD_BUSINESS("Johansson AB", "Drottninggatan 22", "111 51 Stockholm", "SE", "SE987654321", "OrgNr1234567"); ADD_BUSINESS("Nagy Kft.", "Andrássy út 60", "1062 Budapest", "HU", "HU123987654", "Cégjegyzékszám123"); ADD_BUSINESS("Santos Lda.", "Avenida da Liberdade 50", "1250-142 Lisboa", "PT", "PT321789654", "NIPC123456789"); ADD_BUSINESS("Dupuis SARL", "8 Rue Saint-Denis", "75001 Paris", "FR", "FR456123789", "SIREN123456"); ADD_BUSINESS("Müller & Söhne GmbH", "Leipziger Platz 8", "10117 Berlin", "DE", "DE654987321", "HRB987654"); ADD_BUSINESS("Romano Srl", "Via Garibaldi 14", "16124 Genova", "IT", "IT321654987", "GE1239876"); ADD_BUSINESS("López Asociados", "Plaza del Pilar 6", "50003 Zaragoza", "ES", "ES789321654", "CIFC654321"); ADD_BUSINESS("Laurent & Fils", "15 Boulevard Haussmann", "75009 Paris", "FR", "FR987321654", "SIRET654987"); ADD_BUSINESS("Kowalczyk Sp. z o.o.", "ul. Piotrkowska 55", "90-001 Łódź", "PL", "PL123456789", "REGON654321"); ADD_BUSINESS("Nielsen ApS", "Nørregade 12", "1165 København", "DK", "DK789456123", "CVR87654321"); ADD_BUSINESS("Lindberg AB", "Vasagatan 18", "111 20 Stockholm", "SE", "SE456789123", "OrgNr7654321"); ADD_BUSINESS("Szabó Kft.", "Kossuth Lajos tér 1", "1055 Budapest", "HU", "HU987123654", "Cégjegyzékszám654321"); ADD_BUSINESS("Costa Lda.", "Rua do Ouro 24", "1100-063 Lisboa", "PT", "PT654123987", "NIPC987654321"); ADD_BUSINESS("Moreau SARL", "3 Place de la République", "75011 Paris", "FR", "FR321456987", "SIREN789123"); ADD_PROJECT("eCommerce"); ADD_PROJECT("Retail store #1"); ADD_PROJECT("Retail store #2"); ADD_PROJECT("Kayak rental"); // Company info. strops_copy(g_administration.company_info.name, "Aldrik Ramaekers", sizeof(g_administration.company_info.name)); strops_copy(g_administration.company_info.address.address1, "Keerderstraat 81", sizeof(g_administration.company_info.address.address1)); strops_copy(g_administration.company_info.address.address2, "6226XW Maastricht", sizeof(g_administration.company_info.address.address2)); strops_copy(g_administration.company_info.address.country_code, "NL", sizeof(g_administration.company_info.address.country_code)); } void administration_create() { g_administration.next_id = 1; g_administration.next_sequence_number = 1; list_init(&g_administration.contacts); list_init(&g_administration.projects); list_init(&g_administration.tax_brackets); list_init(&g_administration.cost_centers); strops_copy(g_administration.path, "", sizeof(g_administration.path)); snprintf(g_administration.company_info.id, sizeof(g_administration.company_info.id), "C/%d", administration_create_id()); g_administration.next_id++; administration_create_default_tax_brackets(); administration_create_default_cost_centers(); administration_create_debug_data(); } void administration_destroy() { list_destroy(&g_administration.contacts); list_destroy(&g_administration.projects); list_destroy(&g_administration.tax_brackets); list_destroy(&g_administration.cost_centers); } bool administration_create_contact(contact data) { if (!administration_is_contact_valid(data)) return false; contact* new_contact = (contact*)malloc(sizeof(contact)); memcpy((void*)new_contact, (void*)&data, sizeof(contact)); list_append(&g_administration.contacts, new_contact); g_administration.next_id++; return true; } bool administration_can_contact_be_deleted(contact data) { (void)data; // TODO return true; } bool administration_update_contact(contact data) { if (!administration_is_contact_valid(data)) return false; list_iterator_start(&g_administration.contacts); while (list_iterator_hasnext(&g_administration.contacts)) { contact* c = (contact *)list_iterator_next(&g_administration.contacts); if (strcmp(c->id, data.id) == 0) { memcpy(c, &data, sizeof(data)); list_iterator_stop(&g_administration.contacts); return true; } } list_iterator_stop(&g_administration.contacts); return false; } bool administration_remove_contact(contact data) { list_iterator_start(&g_administration.contacts); while (list_iterator_hasnext(&g_administration.contacts)) { contact* c = (contact *)list_iterator_next(&g_administration.contacts); if (strcmp(c->id, data.id) == 0) { list_iterator_stop(&g_administration.contacts); list_delete(&g_administration.contacts, c); return true; } } list_iterator_stop(&g_administration.contacts); return false; } u32 administration_get_contact_count() { return list_size(&g_administration.contacts); } u32 administration_get_contacts(u32 page_index, u32 page_size, contact* buffer) { assert(buffer); u32 write_cursor = 0; u32 read_start = page_index * page_size; list_iterator_start(&g_administration.contacts); while (list_iterator_hasnext(&g_administration.contacts)) { contact c = *(contact *)list_iterator_next(&g_administration.contacts); if (g_administration.contacts.iter_pos <= read_start) continue; buffer[write_cursor++] = c; if (write_cursor >= page_size) break; } list_iterator_stop(&g_administration.contacts); return write_cursor; } int administration_get_contact_recommendations(contact* buffer, int buf_size, char* name) { int write_cursor = 0; if (name[0] == '\0') return 0; list_iterator_start(&g_administration.contacts); while (list_iterator_hasnext(&g_administration.contacts)) { contact c = *(contact *)list_iterator_next(&g_administration.contacts); if (strops_stristr(c.name, name)) { buffer[write_cursor++] = c; if (write_cursor >= buf_size) break; } } list_iterator_stop(&g_administration.contacts); return write_cursor; } char* administration_get_file_path() { return g_administration.path; } u32 administration_get_project_count() { return list_size(&g_administration.projects); } u32 administration_get_billing_items_count(invoice* invoice) { return list_size(&invoice->billing_items); } u32 administration_get_all_billing_items_for_invoice(invoice* invoice, billing_item* buffer) { u32 write_cursor = 0; list_iterator_start(&invoice->billing_items); while (list_iterator_hasnext(&invoice->billing_items)) { billing_item c = *(billing_item *)list_iterator_next(&invoice->billing_items); buffer[write_cursor++] = c; } list_iterator_stop(&invoice->billing_items); return write_cursor; } u32 administration_get_all_projects(project* buffer) { u32 write_cursor = 0; list_iterator_start(&g_administration.projects); while (list_iterator_hasnext(&g_administration.projects)) { project c = *(project *)list_iterator_next(&g_administration.projects); buffer[write_cursor++] = c; } list_iterator_stop(&g_administration.projects); return write_cursor; } u32 administration_get_projects(u32 page_index, u32 page_size, project* buffer) { assert(buffer); u32 write_cursor = 0; u32 read_start = page_index * page_size; list_iterator_start(&g_administration.projects); while (list_iterator_hasnext(&g_administration.projects)) { project c = *(project *)list_iterator_next(&g_administration.projects); if (g_administration.projects.iter_pos <= read_start) continue; buffer[write_cursor++] = c; if (write_cursor >= page_size) break; } list_iterator_stop(&g_administration.projects); return write_cursor; } void administration_cancel_project(project data) { data.end_date = time(NULL); data.state = project_state::PROJECT_CANCELLED; administration_update_project(data); } bool administration_is_project_valid(project data) { return strlen(data.description) > 0; } char* administration_project_get_status_string(project data) { switch(data.state) { case project_state::PROJECT_RUNNING: return "project.state.running"; case project_state::PROJECT_PAUSED: return "project.state.paused"; case project_state::PROJECT_CANCELLED: return "project.state.cancelled"; default: assert(0); break; } return ""; } bool administration_create_project(project data) { if (!administration_is_project_valid(data)) return false; data.state = project_state::PROJECT_RUNNING; data.start_date = time(NULL); data.end_date = 0; project* new_project = (project*)malloc(sizeof(project)); memcpy((void*)new_project, (void*)&data, sizeof(project)); list_append(&g_administration.projects, new_project); g_administration.next_id++; return true; } bool administration_update_project(project data) { if (!administration_is_project_valid(data)) return false; list_iterator_start(&g_administration.projects); while (list_iterator_hasnext(&g_administration.projects)) { project* c = (project *)list_iterator_next(&g_administration.projects); if (strcmp(c->id, data.id) == 0) { memcpy(c, &data, sizeof(data)); list_iterator_stop(&g_administration.projects); return true; } } list_iterator_stop(&g_administration.projects); return false; } bool administration_remove_project(project data) { list_iterator_start(&g_administration.projects); while (list_iterator_hasnext(&g_administration.projects)) { project* c = (project *)list_iterator_next(&g_administration.projects); if (strcmp(c->id, data.id) == 0) { list_iterator_stop(&g_administration.projects); list_delete(&g_administration.projects, c); return true; } } list_iterator_stop(&g_administration.projects); return false; } contact administration_get_company_info() { return g_administration.company_info; } void administration_set_company_info(contact data) { g_administration.company_info = data; } u32 administration_get_tax_bracket_count() { return list_size(&g_administration.tax_brackets); } bool administration_add_tax_bracket(country_tax_bracket data) { ADD_BRACKET(data.country_code, data.rate, ""); list_attributes_comparator(&g_administration.tax_brackets, compare_tax_countries); list_sort(&g_administration.tax_brackets, -1); return true; } u32 administration_get_tax_brackets_for_country(country_tax_bracket* buffer, char* country_code) { assert(buffer); u32 write_cursor = 0; list_iterator_start(&g_administration.tax_brackets); while (list_iterator_hasnext(&g_administration.tax_brackets)) { country_tax_bracket c = *(country_tax_bracket *)list_iterator_next(&g_administration.tax_brackets); if (strcmp(c.country_code, country_code) == 0 || strcmp(c.country_code, "00") == 0) buffer[write_cursor++] = c; } list_iterator_stop(&g_administration.tax_brackets); return write_cursor; } u32 administration_get_tax_brackets(country_tax_bracket* buffer) { assert(buffer); u32 write_cursor = 0; list_iterator_start(&g_administration.tax_brackets); while (list_iterator_hasnext(&g_administration.tax_brackets)) { country_tax_bracket c = *(country_tax_bracket *)list_iterator_next(&g_administration.tax_brackets); buffer[write_cursor++] = c; } list_iterator_stop(&g_administration.tax_brackets); return write_cursor; } bool administration_update_tax_bracket(country_tax_bracket data) { list_iterator_start(&g_administration.tax_brackets); while (list_iterator_hasnext(&g_administration.tax_brackets)) { country_tax_bracket* c = (country_tax_bracket *)list_iterator_next(&g_administration.tax_brackets); if (strcmp(c->id, data.id) == 0) { memcpy(c, &data, sizeof(data)); list_iterator_stop(&g_administration.tax_brackets); return true; } } list_iterator_stop(&g_administration.tax_brackets); return false; } u32 administration_get_cost_center_count() { return list_size(&g_administration.cost_centers); } u32 administration_get_cost_centers(cost_center* buffer) { assert(buffer); u32 write_cursor = 0; list_iterator_start(&g_administration.cost_centers); while (list_iterator_hasnext(&g_administration.cost_centers)) { cost_center c = *(cost_center *)list_iterator_next(&g_administration.cost_centers); buffer[write_cursor++] = c; } list_iterator_stop(&g_administration.cost_centers); return write_cursor; } static bool administration_get_cost_center_by_code(char* code, cost_center* buffer) { bool result = false; list_iterator_start(&g_administration.cost_centers); while (list_iterator_hasnext(&g_administration.cost_centers)) { cost_center c = *(cost_center *)list_iterator_next(&g_administration.cost_centers); *buffer = c; if (strcmp(code, c.code) == 0) { result = true; break; } } list_iterator_stop(&g_administration.cost_centers); return result; } bool administration_verify_cost_center_description(char* text) { return strlen(text) != 0; } bool administration_verify_cost_center_code(char* code) { if (strlen(code) == 0) return false; cost_center cost_center; bool found = administration_get_cost_center_by_code(code, &cost_center); return !found; } bool administration_add_cost_center(cost_center data) { cost_center cs; bool found = administration_get_cost_center_by_code(data.code, &cs); if (found) return false; cost_center* tb = (cost_center*)malloc(sizeof(cost_center)); snprintf(tb->id, sizeof(tb->id), "E/%d", administration_create_id()); memcpy(tb->description, data.description, sizeof(tb->description)); memcpy(tb->code, data.code, sizeof(tb->code)); list_append(&g_administration.cost_centers, tb); g_administration.next_id++; return true; } bool administration_update_cost_center(cost_center data) { list_iterator_start(&g_administration.cost_centers); while (list_iterator_hasnext(&g_administration.cost_centers)) { cost_center* c = (cost_center *)list_iterator_next(&g_administration.cost_centers); if (strcmp(c->id, data.id) == 0) { memcpy(c, &data, sizeof(data)); list_iterator_stop(&g_administration.cost_centers); return true; } } list_iterator_stop(&g_administration.cost_centers); return false; } bool administration_is_contact_valid(contact data) { if (data.type == contact_type::CONTACT_CONSUMER) { return strlen(data.name) > 0 && strlen(data.address.address1) > 0 && strlen(data.address.address2) > 0 && strlen(data.address.country_code) > 0; } else if (data.type == contact_type::CONTACT_BUSINESS) { return strlen(data.name) > 0 && strlen(data.address.address1) > 0 && strlen(data.address.address2) > 0 && strlen(data.address.country_code) > 0 && strlen(data.taxid) > 0 && strlen(data.businessid); } return false; } static s32 administration_create_sequence_number() { return g_administration.next_sequence_number; } static time_t administration_get_default_invoice_expire_duration() { return (30 * 24 * 60 * 60); // 30 days } billing_item administration_create_empty_billing_item() { billing_item item; memset(&item, 0, sizeof(billing_item)); item.amount = 1; return item; } bool administration_add_billing_item_to_invoice(invoice* invoice, billing_item item) { billing_item* tb = (billing_item*)malloc(sizeof(billing_item)); memcpy(tb, &item, sizeof(billing_item)); snprintf(tb->id, sizeof(tb->id), "B/%d", administration_create_id()); strops_copy(tb->invoice_id, invoice->id, sizeof(tb->invoice_id)); list_append(&invoice->billing_items, tb); strops_copy(tb->currency, invoice->currency, CURRENCY_LENGTH); // Set billing item currency to invoice currency. g_administration.next_id++; return true; } static char* administration_get_default_currency_for_country(char* country_code) { if (country_code == NULL || strlen(country_code) != 2) return "EUR"; // default // Non-euro EU currencies if (strcmp(country_code, "BG") == 0) return "BGN"; // Bulgaria else if (strcmp(country_code, "CZ") == 0) return "CZK"; // Czechia else if (strcmp(country_code, "DK") == 0) return "DKK"; // Denmark else if (strcmp(country_code, "HU") == 0) return "HUF"; // Hungary else if (strcmp(country_code, "PL") == 0) return "PLN"; // Poland else if (strcmp(country_code, "RO") == 0) return "RON"; // Romania else if (strcmp(country_code, "SE") == 0) return "SEK"; // Sweden // Eurozone members else if (strcmp(country_code, "AT") == 0) return "EUR"; // Austria else if (strcmp(country_code, "BE") == 0) return "EUR"; // Belgium else if (strcmp(country_code, "CY") == 0) return "EUR"; // Cyprus else if (strcmp(country_code, "DE") == 0) return "EUR"; // Germany else if (strcmp(country_code, "EE") == 0) return "EUR"; // Estonia else if (strcmp(country_code, "ES") == 0) return "EUR"; // Spain else if (strcmp(country_code, "FI") == 0) return "EUR"; // Finland else if (strcmp(country_code, "FR") == 0) return "EUR"; // France else if (strcmp(country_code, "GR") == 0) return "EUR"; // Greece else if (strcmp(country_code, "HR") == 0) return "EUR"; // Croatia else if (strcmp(country_code, "IE") == 0) return "EUR"; // Ireland else if (strcmp(country_code, "IT") == 0) return "EUR"; // Italy else if (strcmp(country_code, "LT") == 0) return "EUR"; // Lithuania else if (strcmp(country_code, "LU") == 0) return "EUR"; // Luxembourg else if (strcmp(country_code, "LV") == 0) return "EUR"; // Latvia else if (strcmp(country_code, "MT") == 0) return "EUR"; // Malta else if (strcmp(country_code, "NL") == 0) return "EUR"; // Netherlands else if (strcmp(country_code, "PT") == 0) return "EUR"; // Portugal else if (strcmp(country_code, "SI") == 0) return "EUR"; // Slovenia else if (strcmp(country_code, "SK") == 0) return "EUR"; // Slovakia // Default fallback return "EUR"; } invoice administration_create_empty_invoice() { invoice result; memset(&result, 0, sizeof(invoice)); 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(); list_init(&result.billing_items); // @leak strops_copy(result.currency, administration_get_default_currency_for_country(g_administration.company_info.address.country_code), CURRENCY_LENGTH); return result; } contact administration_create_empty_contact() { contact result; memset(&result, 0, sizeof(contact)); snprintf(result.id, sizeof(result.id), "C/%d", administration_create_id()); return result; } project administration_create_empty_project() { project result; memset(&result, 0, sizeof(project)); snprintf(result.id, sizeof(result.id), "P/%d", administration_create_id()); return result; } static bool administration_get_tax_bracket_by_id(country_tax_bracket* buffer, char* id) { assert(buffer); list_iterator_start(&g_administration.tax_brackets); while (list_iterator_hasnext(&g_administration.tax_brackets)) { country_tax_bracket c = *(country_tax_bracket *)list_iterator_next(&g_administration.tax_brackets); if (strcmp(c.id, id) == 0) { *buffer = c; list_iterator_stop(&g_administration.tax_brackets); return true; } } list_iterator_stop(&g_administration.tax_brackets); return false; } static void administration_recalculate_billing_item_totals(billing_item* item) { if (item->amount_is_percentage) { item->net = item->net_per_item * (item->amount / 100.0f); } else { item->net = item->net_per_item * item->amount; } if (item->discount != 0) { if (item->discount_is_percentage) { item->net -= item->net * (item->discount / 100.0f); } else { item->net -= item->discount; } } country_tax_bracket bracket; if (administration_get_tax_bracket_by_id(&bracket, item->tax_bracket_id)) { item->tax = item->net * (bracket.rate/100.0f); } item->total = item->net + item->tax; } bool administration_update_billing_item_of_invoice(invoice* invoice, billing_item item) { list_iterator_start(&invoice->billing_items); while (list_iterator_hasnext(&invoice->billing_items)) { billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items); if (strcmp(c->id, item.id) == 0) { memcpy(c, &item, sizeof(billing_item)); administration_recalculate_billing_item_totals(c); list_iterator_stop(&invoice->billing_items); return true; } } list_iterator_stop(&invoice->billing_items); return false; } void administration_invoice_set_currency(invoice* invoice, char* currency) { strops_copy(invoice->currency, currency, CURRENCY_LENGTH); list_iterator_start(&invoice->billing_items); while (list_iterator_hasnext(&invoice->billing_items)) { billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items); strops_copy(c->currency, currency, CURRENCY_LENGTH); } list_iterator_stop(&invoice->billing_items); }