summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/CHANGES.rst7
-rw-r--r--include/administration.hpp2
-rw-r--r--src/administration.cpp24
-rw-r--r--src/administration_reader.cpp11
-rw-r--r--src/ai_providers/openAI.cpp2
-rw-r--r--src/import_service.cpp49
-rw-r--r--src/ui/ui_invoices.cpp4
7 files changed, 85 insertions, 14 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst
index 2d1bbf1..93fd296 100644
--- a/docs/CHANGES.rst
+++ b/docs/CHANGES.rst
@@ -2,11 +2,10 @@
TODO:
for invoice importing using AI:
- 1. my address data should be editable because import is not perfect
- 4. find billing item tax rate based on paid tax vs total
- 6. set document original and copy path (save file to zip first)
- 7. create a new invoice ID
+ 1. all address data should be editable because import is not perfect
+ 2. file path should not be editable as it is imported
+- toggle on invoice form wether price in inclusive of tax.
- retrieve available balance from AI api & show in settings/services.
- let user choose the model to use in settings/services/ai
- real error logging for OpenAI and importing in general
diff --git a/include/administration.hpp b/include/administration.hpp
index 3d6a88c..25b093a 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -425,6 +425,7 @@ s32 administration_get_next_id();
s32 administration_get_next_sequence_number();
char* administration_get_currency_symbol_for_currency(char* code);
char* administration_get_default_currency();
+time_t administration_get_default_invoice_expire_duration();
ai_service administration_get_ai_service();
void administration_set_file_path(char* path);
@@ -478,6 +479,7 @@ a_err administration_tax_rate_import(tax_rate data);
a_err administration_tax_rate_add(tax_rate data);
a_err administration_tax_rate_update(tax_rate data);
+a_err administration_tax_rate_get_by_shorthandle(tax_rate* buffer, char* handle);
a_err administration_tax_rate_get_by_id(tax_rate* buffer, char* id);
u32 administration_tax_rate_get_all(tax_rate* buffer);
u32 administration_tax_rate_get_by_country(tax_rate* buffer, u32 code_count, char** country_codes);
diff --git a/src/administration.cpp b/src/administration.cpp
index 231953b..3be34da 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -49,7 +49,7 @@ static int compare_tax_countries(const void *a, const void *b)
return strcmp(objA->country_code, objB->country_code);
}
-static time_t administration_get_default_invoice_expire_duration()
+time_t administration_get_default_invoice_expire_duration()
{
return (30 * 24 * 60 * 60); // 30 days
}
@@ -1115,6 +1115,28 @@ tax_rate administration_tax_rate_create_empty()
return result;
}
+a_err administration_tax_rate_get_by_shorthandle(tax_rate* buffer, char* handle)
+{
+ assert(buffer);
+
+ list_iterator_start(&g_administration.tax_rates);
+ while (list_iterator_hasnext(&g_administration.tax_rates)) {
+ tax_rate c = *(tax_rate *)list_iterator_next(&g_administration.tax_rates);
+
+ char compare_str[20];
+ snprintf(compare_str, 20, "%s/%.2f", c.country_code, c.rate);
+ if (strcmp(compare_str, handle) == 0)
+ {
+ *buffer = c;
+ list_iterator_stop(&g_administration.tax_rates);
+ return A_ERR_SUCCESS;
+ }
+ }
+ list_iterator_stop(&g_administration.tax_rates);
+
+ return A_ERR_NOT_FOUND;
+}
+
a_err administration_tax_rate_get_by_id(tax_rate* buffer, char* id)
{
assert(buffer);
diff --git a/src/administration_reader.cpp b/src/administration_reader.cpp
index f316f40..a72ba8a 100644
--- a/src/administration_reader.cpp
+++ b/src/administration_reader.cpp
@@ -232,6 +232,17 @@ bool administration_reader_read_invoice_from_xml(invoice* result, char* buffer,
bi.discount = xml_get_float_x(child, "cac:AllowanceCharge", "cbc:MultiplierFactorNumeric", 0);
}
+ // Import service could set tax rate id to shorthandle.
+ tax_rate tax_rate;
+ if (administration_tax_rate_get_by_id(&tax_rate, bi.tax_rate_id) == A_ERR_NOT_FOUND) {
+ if (administration_tax_rate_get_by_shorthandle(&tax_rate, bi.tax_rate_id) == A_ERR_SUCCESS) {
+ strops_copy(bi.tax_rate_id, tax_rate.id, MAX_LEN_ID);
+ }
+ else {
+ strops_copy(bi.tax_rate_id, "", MAX_LEN_ID);
+ }
+ }
+
administration_billing_item_import_to_invoice(&data, bi);
}
diff --git a/src/ai_providers/openAI.cpp b/src/ai_providers/openAI.cpp
index 7675e8b..7ebd680 100644
--- a/src/ai_providers/openAI.cpp
+++ b/src/ai_providers/openAI.cpp
@@ -28,7 +28,7 @@
static bool _openAI_query_with_file(char* query, size_t query_length, char* file_id, char** response)
{
- #if 0
+ #if 1
const char *api_key = administration_get_ai_service().api_key_public;
httplib::SSLClient cli("api.openai.com", 443);
diff --git a/src/import_service.cpp b/src/import_service.cpp
index b7a519c..0aaa839 100644
--- a/src/import_service.cpp
+++ b/src/import_service.cpp
@@ -69,9 +69,33 @@ static int _ai_document_to_invoice_t(void *arg) {
strops_replace(template_buffer, 50000, "{{INVOICE_LINE_LIST}}", peppol_invoice_line_template);
char* ai_query =
- "\n\nI have provided a file containing an invoice. Fill in the above Peppol 3.0 template with the information from the invoice."
- "Do not add any fields to the template. If you can't find data for a given field, leave it empty. Do not make up any information."
- "Only return the filled out template in valid XML format. Nothing else.\n";
+ "\n\nI have provided a file containing an invoice. Fill in the above Peppol 3.0 template with the information from the invoice.\n"
+ "Do not add any fields to the template. If you can't find data for a given field, leave it empty. Do not make up any information.\n"
+ "Only return the filled out template in valid XML format. Nothing else.\n\n"
+ "{{LINE_AMOUNT}} equals the net paid amount for an order line.\n"
+ "{{UNIT_PRICE}} is the net price per unit in an order line.\n"
+ "{{QUANTITY}} is the amount of units per order line. If this is not defined, default to 1.\n"
+ "If {{UNIT_PRICE}} is less than 1.00 and {{QUANTITY}} is more than 10, {{QUANTITY}} should equal 1, {{UNIT_PRICE}} should equal {{LINE_AMOUNT}} and {{ITEM_NAME}} should include the original {{QUANTITY}}.\n" // High quantity, small price, might result in incorrect unit price. e.g. 700x resistor for 2,00 total.
+ "{{UNIT_CODE}} should always be 'X' unless you know for sure know the line item amount is defined as a percentage, in which case it should be '%'.\n"
+ "Every invoice line will atleast have {{LINE_AMOUNT}} and {{ITEM_NAME}} defined.\n"
+ "{{LINE_TAX_PERCENT}} is the tax rate for the line item. This could also be described as VAT rate. Often an invoice only has 1 tax rate defined intstead of per line item.\n"
+ "{{LINE_TAX_ID}} should be set to the country code and tax rate, in the format 'CC/PP' where CC is the 2 letter country code and PP is the tax rate as a number with 2 decimals.\n"
+ "If a line item is taxted with vat reverse Charge, {{LINE_TAX_ID}} should be set to '00/AE'.\n"
+ "If a line item is exempt from Tax, {{LINE_TAX_ID}} should be set to '00/E'.\n"
+ "If a line item is categorized as zero rated goods, {{LINE_TAX_ID}} should be set to '00/Z'.\n"
+ "If a line item is a service outside scope of tax, {{LINE_TAX_ID}} should be set to '00/O'.\n"
+ "If a line item is VAT exempt for EEA intra-community supply of goods and services, {{LINE_TAX_ID}} should be set to '00/K'.\n"
+ "All of there tax rates can be declared as per line item, or per invoice.\n"
+ "If you can find the tax rate for 1 line item but not another, assume they are taxed at the same rate and their {{LINE_TAX_ID}} should match.\n"
+ "If shipping costs are provided, these should also be added to the result as a cac:InvoiceLine.\n"
+ "{{INVOICE_SEQUENCE_ID}} should be set to the provided invoice id or invoice number. This is always defined.\n"
+ "{{ISSUE_DATE}} is the date the invoice was issued and should be stored in format 'YYYY-MM-DD'.\n"
+ "{{DUE_DATE}} is the date the invoice is due and should be stored in format 'YYYY-MM-DD'. If the due date is not defined, {{DUE_DATE}} should equal 0.\n"
+ "{{DELIVERY_DATE}} might be defined and should be stored in format 'YYYY-MM-DD'. If the delivery date is not defined, {{DELIVERY_DATE}} should equal 0.\n"
+ "cac:AccountingSupplierParty contains all information of the supplier. This information might be under the section 'Supplier', 'Seller', 'Sold by' or something similar.\n"
+ "cac:AccountingCustomerParty contains all information of the customer. This information might be under the section 'Customer', 'Ordered by', 'Billing address' or something similar.\n"
+ "cac:Delivery contains the delivery address for physical goods. This information might be under the section 'Shipping address', 'Shipped to' or something similar. If this is not explicitly set, leave this section empty.\n"
+ ;
size_t query_len = strlen(template_buffer);
strncpy(template_buffer + query_len, ai_query, query_buffer_len - query_len);
@@ -92,17 +116,30 @@ static int _ai_document_to_invoice_t(void *arg) {
return 0;
}
- invoice tmp = administration_invoice_create_empty();
-
inv.status = invoice_status::INVOICE_RECEIVED;
- strops_copy(inv.id, tmp.id, MAX_LEN_ID); // TODO next_id is not being incremented
+
+ // Set customer or supplier depending on incomming or outgoing.
contact my_info = administration_company_info_get();
memcpy(&inv.customer, &my_info, sizeof(contact));
strops_copy(inv.customer.id, MY_COMPANY_ID, MAX_LEN_ID);
+ // Project and cost centers cannot be interpreted from file so are set to 0.
+ strops_copy(inv.project_id, "", MAX_LEN_ID);
+ strops_copy(inv.cost_center_id, "", MAX_LEN_ID);
+
+ // Set document references and save copy to disk.
strops_copy(inv.document.original_path, file_path, MAX_LEN_PATH);
strops_copy(inv.document.copy_path, "", MAX_LEN_PATH);
+ // Set dates.
+ if (inv.expires_at == 0) {
+ inv.expires_at = inv.issued_at + administration_get_default_invoice_expire_duration();
+ }
+
+ if (inv.delivered_at == 0) {
+ inv.delivered_at = inv.issued_at;
+ }
+
free(template_buffer);
free(response);
diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp
index 1be0c82..4a087be 100644
--- a/src/ui/ui_invoices.cpp
+++ b/src/ui/ui_invoices.cpp
@@ -99,11 +99,11 @@ void draw_invoice_items_form(invoice* invoice)
ImGui::TableSetColumnIndex(3);
ImGui::PushItemWidth(-1);
- ImGui::InputFloat("##price", &item.net_per_item, 0.0f, 0.0f, "%.2f");
+ ImGui::InputFloat("##price", &item.net_per_item, 0.0f, 0.0f, "%.3f");
ImGui::PopItemWidth();
ImGui::TableSetColumnIndex(4);
- ImGui::InputFloat("##discount", &item.discount, 0.0f, 0.0f, "%.2f");
+ ImGui::InputFloat("##discount", &item.discount, 0.0f, 0.0f, "%.3f");
ImGui::SameLine();
ImGui::FormToggleCombo(&item.discount_is_percentage, item.currency, "%");