summaryrefslogtreecommitdiff
path: root/src/importer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/importer.cpp')
-rw-r--r--src/importer.cpp263
1 files changed, 197 insertions, 66 deletions
diff --git a/src/importer.cpp b/src/importer.cpp
index 194913a..4f78d20 100644
--- a/src/importer.cpp
+++ b/src/importer.cpp
@@ -34,20 +34,182 @@ importer::ai_provider_impl importer::get_ai_provider_implementation(ai_provider
switch(provider)
{
case AI_PROVIDER_OPENAI: return _chatgpt_api_provider;
- case AI_PROVIDER_DEEPSEEK: return _deepseek_api_provider;
+ //case AI_PROVIDER_DEEPSEEK: return _deepseek_api_provider;
default: assert(0); break;
}
return importer::ai_provider_impl {0};
}
-static int _ai_document_to_invoice_t(void *arg) {
+static void _batch_query_response_handler(invoice* buffer, char* json)
+{
+ int alloc_size = 1000;
+ char* rb = (char*)memops::alloc(alloc_size);
+ strops::get_json_value(json, "query_id", rb, alloc_size);
+
+ if (strops::equals(rb, "-1")) return; // ignore
+
+ else if (strops::equals(rb, "1")) {
+ strops::get_json_value(json, "sequential_number", rb, alloc_size);
+ strops::copy(buffer->sequential_number, rb, sizeof(buffer->sequential_number));
+ }
+ else if (strops::equals(rb, "2")) {
+ strops::get_json_value(json, "issued_at", rb, alloc_size);
+ buffer->issued_at = strtoll(rb, NULL, 10);
+ }
+ else if (strops::equals(rb, "3")) {
+ strops::get_json_value(json, "expires_at", rb, alloc_size);
+ buffer->expires_at = strtoll(rb, NULL, 10);
+ }
+ else if (strops::equals(rb, "4")) {
+ strops::get_json_value(json, "currency_code", rb, alloc_size);
+ administration::invoice_set_currency(buffer, rb);
+ }
+ else if (strops::equals(rb, "5")) {
+ strops::get_json_value(json, "address1", rb, alloc_size);
+ strops::copy(buffer->supplier.address.address1, rb, sizeof(buffer->supplier.address.address1));
+
+ strops::get_json_value(json, "address2", rb, alloc_size);
+ strops::copy(buffer->supplier.address.address2, rb, sizeof(buffer->supplier.address.address2));
+
+ strops::get_json_value(json, "city", rb, alloc_size);
+ strops::copy(buffer->supplier.address.city, rb, sizeof(buffer->supplier.address.city));
+
+ strops::get_json_value(json, "postal", rb, alloc_size);
+ strops::copy(buffer->supplier.address.postal, rb, sizeof(buffer->supplier.address.postal));
+
+ strops::get_json_value(json, "region", rb, alloc_size);
+ strops::copy(buffer->supplier.address.region, rb, sizeof(buffer->supplier.address.region));
+
+ strops::get_json_value(json, "country_code", rb, alloc_size);
+ strops::copy(buffer->supplier.address.country_code, rb, sizeof(buffer->supplier.address.country_code));
+
+ strops::get_json_value(json, "is_business", rb, alloc_size);
+ buffer->supplier.type = (strops::equals(rb, "true")) ? contact_type::CONTACT_BUSINESS : contact_type::CONTACT_CONSUMER;
+
+ strops::get_json_value(json, "name", rb, alloc_size);
+ strops::copy(buffer->supplier.name, rb, sizeof(buffer->supplier.name));
+
+ strops::get_json_value(json, "taxid", rb, alloc_size);
+ strops::copy(buffer->supplier.taxid, rb, sizeof(buffer->supplier.taxid));
+
+ strops::get_json_value(json, "businessid", rb, alloc_size);
+ strops::copy(buffer->supplier.businessid, rb, sizeof(buffer->supplier.businessid));
+
+ strops::get_json_value(json, "email", rb, alloc_size);
+ strops::copy(buffer->supplier.email, rb, sizeof(buffer->supplier.email));
+
+ strops::get_json_value(json, "phone_number", rb, alloc_size);
+ strops::copy(buffer->supplier.phone_number, rb, sizeof(buffer->supplier.phone_number));
+
+ strops::get_json_value(json, "bank_account", rb, alloc_size);
+ strops::copy(buffer->supplier.bank_account, rb, sizeof(buffer->supplier.bank_account));
+ }
+ else if (strops::equals(rb, "6")) {
+ strops::get_json_value(json, "address1", rb, alloc_size);
+ strops::copy(buffer->customer.address.address1, rb, sizeof(buffer->customer.address.address1));
+
+ strops::get_json_value(json, "address2", rb, alloc_size);
+ strops::copy(buffer->customer.address.address2, rb, sizeof(buffer->customer.address.address2));
+
+ strops::get_json_value(json, "city", rb, alloc_size);
+ strops::copy(buffer->customer.address.city, rb, sizeof(buffer->customer.address.city));
+
+ strops::get_json_value(json, "postal", rb, alloc_size);
+ strops::copy(buffer->customer.address.postal, rb, sizeof(buffer->customer.address.postal));
+
+ strops::get_json_value(json, "region", rb, alloc_size);
+ strops::copy(buffer->customer.address.region, rb, sizeof(buffer->customer.address.region));
+
+ strops::get_json_value(json, "country_code", rb, alloc_size);
+ strops::copy(buffer->customer.address.country_code, rb, sizeof(buffer->customer.address.country_code));
+
+ strops::get_json_value(json, "is_business", rb, alloc_size);
+ buffer->customer.type = (strops::equals(rb, "true")) ? contact_type::CONTACT_BUSINESS : contact_type::CONTACT_CONSUMER;
+
+ strops::get_json_value(json, "name", rb, alloc_size);
+ strops::copy(buffer->customer.name, rb, sizeof(buffer->customer.name));
+
+ strops::get_json_value(json, "taxid", rb, alloc_size);
+ strops::copy(buffer->customer.taxid, rb, sizeof(buffer->customer.taxid));
+
+ strops::get_json_value(json, "businessid", rb, alloc_size);
+ strops::copy(buffer->customer.businessid, rb, sizeof(buffer->customer.businessid));
+
+ strops::get_json_value(json, "email", rb, alloc_size);
+ strops::copy(buffer->customer.email, rb, sizeof(buffer->customer.email));
+
+ strops::get_json_value(json, "phone_number", rb, alloc_size);
+ strops::copy(buffer->customer.phone_number, rb, sizeof(buffer->customer.phone_number));
+
+ strops::get_json_value(json, "bank_account", rb, alloc_size);
+ strops::copy(buffer->customer.bank_account, rb, sizeof(buffer->customer.bank_account));
+ }
+ else if (strops::equals(rb, "7")) {
+ strops::get_json_value(json, "address1", rb, alloc_size);
+ strops::copy(buffer->addressee.address.address1, rb, sizeof(buffer->addressee.address.address1));
+
+ strops::get_json_value(json, "address2", rb, alloc_size);
+ strops::copy(buffer->addressee.address.address2, rb, sizeof(buffer->addressee.address.address2));
+
+ strops::get_json_value(json, "city", rb, alloc_size);
+ strops::copy(buffer->addressee.address.city, rb, sizeof(buffer->addressee.address.city));
+
+ strops::get_json_value(json, "postal", rb, alloc_size);
+ strops::copy(buffer->addressee.address.postal, rb, sizeof(buffer->addressee.address.postal));
+
+ strops::get_json_value(json, "region", rb, alloc_size);
+ strops::copy(buffer->addressee.address.region, rb, sizeof(buffer->addressee.address.region));
+
+ strops::get_json_value(json, "country_code", rb, alloc_size);
+ strops::copy(buffer->addressee.address.country_code, rb, sizeof(buffer->addressee.address.country_code));
+
+ strops::get_json_value(json, "name", rb, alloc_size);
+ strops::copy(buffer->addressee.name, rb, sizeof(buffer->addressee.name));
+ }
+ else if (strops::equals(rb, "8")) {
+ strops::get_json_value(json, "item_count", rb, alloc_size);
+ u32 item_count = strtol(rb, NULL, 10);
+
+ for (u32 i = 0; i < item_count; i++)
+ {
+ billing_item item = administration::billing_item_create_empty();
+ strops::get_json_value(json, "amount", rb, alloc_size, i);
+ item.amount = strtof(rb, NULL);
+
+ strops::get_json_value(json, "amount_is_percentage", rb, alloc_size, i);
+ item.amount_is_percentage = strops::equals(rb, "true");
+
+ strops::get_json_value(json, "description", rb, alloc_size, i);
+ strops::copy(item.description, rb, sizeof(item.description));
+
+ strops::get_json_value(json, "price_per_item", rb, alloc_size, i);
+ item.net_per_item = strtof(rb, NULL);
+
+ strops::get_json_value(json, "discount", rb, alloc_size, i);
+ item.discount = strtof(rb, NULL);
+
+ strops::get_json_value(json, "discount_is_percentage", rb, alloc_size, i);
+ item.discount_is_percentage = strops::equals(rb, "true");
+
+ administration::billing_item_add_to_invoice(buffer, item);
+ }
+
+ }
+
+ memops::unalloc(rb);
+
+ return;
+}
+
+static int _ai_document_to_invoice_t(void *arg)
+{
importer::invoice_request* request = (importer::invoice_request*)arg;
char* file_path = request->file_path;
importer::ai_provider_impl impl = importer::get_ai_provider_implementation(administration::get_ai_service().provider);
request->status = importer::status::IMPORT_UPLOADING_FILE;
-
+
char file_id[100];
if (!impl.upload_file(file_path, file_id, 100)) {
request->status = importer::status::IMPORT_DONE;
@@ -55,68 +217,49 @@ static int _ai_document_to_invoice_t(void *arg) {
return 0;
}
- request->status = importer::status::IMPORT_QUERYING;
-
- size_t query_buffer_len = 50000;
- char* template_buffer = (char*)memops::alloc(query_buffer_len);
- memops::zero(template_buffer, query_buffer_len);
-
- strops::copy(template_buffer, file_template::peppol_invoice_template, query_buffer_len);
- strops::replace(template_buffer, 50000, "{{INVOICE_LINE_LIST}}", file_template::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.\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 = strops::length(template_buffer);
- strops::copy(template_buffer + query_len, ai_query, query_buffer_len - query_len);
-
request->status = importer::status::IMPORT_WAITING_FOR_RESPONSE;
- char* response;
- if (!impl.query_with_file(template_buffer, query_buffer_len, file_id, &response)) {
+ char* queries[] = {
+ "What is the invoice number/ID? Return json containing sequential_number (string), query_id = 1 (string)",
+ "When was this invoice issued? Return json containing issued_at (time_t value), query_id = 2 (string). If not found, issued_at = 0",
+ "When does this invoice expire? Return json containing expires_at (time_t value), query_id = 3 (string). If not found, expires_at = 0",
+ "What currency is this invoice issued in? Look for a currency symbol in one of the billed items. Return json containing currency_code (3 letter code string), query_id = 4 (string)",
+
+ "Who sent the invoice? This information might be under the section 'From, 'Supplier', 'Seller', 'Sold by' or something similar. Return json containing query_id = 5 (string), "
+ " address1 (string, address line 1), address2 (string, address line 2), "
+ " city (string), postal (string), region (string), country_code (string, 2 letter code), is_business (string, 'true' or 'false'), "
+ " name (string), taxid (string, tax identifier number), businessid (string, business identifier number), email (string), "
+ " phone_number (string), bank_account (string)",
+
+ "Who received the invoice? This information might be under the section 'Customer', 'Ordered by', 'Billing address' or something similar. Return json containing query_id = 6 (string), "
+ " address1 (string, address line 1), address2 (string, address line 2), "
+ " city (string), postal (string), region (string), country_code (string, 2 letter code), is_business (string, 'true' or 'false'), "
+ " name (string), taxid (string, tax identifier number), businessid (string, business identifier number), email (string), "
+ " phone_number (string), bank_account (string)",
+
+ "Who received the product? This information might be under the section 'Delivered to', 'shipping address' or something similar. Return json containing query_id = 7 (string), "
+ " address1 (string, address line 1), address2 (string, address line 2), "
+ " city (string), postal (string), region (string), country_code (string, 2 letter code), name (string). "
+ " If the delivery address is not provided, return query_id = -1 (string)",
+
+ "Give me a list of billed items in json format. Return query_id = 8 (string), item_count (string), items (array). For each item, get: "
+ " amount (string, number of items in billing line, default to 1), amount_is_percentage (string, 'true' or 'false'), description (string), price_per_item (string, price with 2 decimals, no currency symbol), "
+ " discount (string, price with 2 decimals, no currency symbol), discount_is_percentage (string, 'true' or 'false')"
+ };
+
+ invoice inv = administration::invoice_create_empty();
+
+ if (!impl.batch_query_with_file(queries, sizeof(queries) / sizeof(char*), file_id, &inv, _batch_query_response_handler)) {
request->status = importer::status::IMPORT_DONE;
request->error = I_ERR_FAILED_QUERY;
return 0;
}
- invoice inv;
- if (!administration_reader::read_invoice_from_xml(&inv, response, strops::length(response))) {
- request->status = importer::status::IMPORT_DONE;
- request->error = I_ERR_FAILED_IMPORT;
- return 0;
- }
-
inv.status = invoice_status::INVOICE_RECEIVED;
// Set customer or supplier depending on incomming or outgoing.
contact my_info = administration::company_info_get();
- memops::copy(&inv.customer, &my_info, sizeof(contact));
+ //memops::copy(&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.
@@ -127,18 +270,6 @@ static int _ai_document_to_invoice_t(void *arg) {
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;
- }
-
- memops::unalloc(template_buffer);
- memops::unalloc(response);
-
request->status = importer::status::IMPORT_DONE;
request->result = administration::invoice_create_copy(&inv);
return 0;