first commit

This commit is contained in:
root
2026-03-14 09:42:12 +00:00
commit 0adbd20c2c
10991 changed files with 1646955 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,338 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import base64
from decimal import Decimal
from functools import wraps
import requests
from trytond.i18n import gettext
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from .exceptions import TyplessCredentialWarning, TyplessError
MIME_TYPES = {
'application/pdf',
'image/jpeg',
'image/png',
'image/tiff',
}
EXTRACT_DATA = 'https://developers.typless.com/api/extract-data'
ADD_DOCUMENT_FEEDBACK = (
'https://developers.typless.com/api/add-document-feedback')
def typless_api(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except requests.HTTPError as e:
error_message = e.args[0]
raise TyplessError(
gettext('document_incoming_ocr_typless'
'.msg_typless_webserver_error',
message=error_message))
return wrapper
def get_best_value(fields, name):
for field in fields:
if field.get('name') == name:
break
else:
return
values = field.get('values', [])
values = sorted(
values, key=lambda v: v.get('confidence_score'), reverse=True)
if values:
return values[0].get('value')
class IncomingOCRService(metaclass=PoolMeta):
__name__ = 'document.incoming.ocr.service'
_states = {
'required': Eval('type') == 'typless',
'invisible': Eval('type') != 'typless',
}
typless_api_key = fields.Char(
"API Key", states=_states,
help="The standard token from Typless settings page.")
typless_document_type = fields.Char(
"Document Type", states=_states,
help="The name of the document type on Typless.")
@classmethod
def __setup__(cls):
super().__setup__()
cls.type.selection.append(('typless', "Typless"))
def match_mime_type(self, mime_type):
match = super().match_mime_type(mime_type)
if self.type == 'typless':
match = mime_type in MIME_TYPES
return match
@typless_api
def _process_typless(self, document):
payload = self._typless_extract_payload(document)
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': self.typless_api_key,
}
response = requests.post(EXTRACT_DATA, json=payload, headers=headers)
response.raise_for_status()
return response.json()
def _typless_extract_payload(self, document):
return {
'file': base64.b64encode(document.data).decode('utf-8'),
'file_name': document.name,
'document_type_name': self.typless_document_type,
}
def _get_document_incoming_typless(self, document):
document_data = {}
if not document.parsed_data:
return document_data
fields = document.parsed_data.get('extracted_fields', [])
document_type = get_best_value(fields, 'document_type')
if document_type is not None:
document_data['document_type'] = document_type
return document_data
def _get_supplier_invoice_typless(self, document):
invoice_data = {}
if not document.parsed_data:
return invoice_data
fields = document.parsed_data.get('extracted_fields', [])
for name in [
'company_name', 'company_tax_identifier', 'supplier_name',
'tax_identifier', 'currency', 'number', 'description',
'invoice_date', 'payment_term_date', 'total_amount',
'purchase_orders']:
value = get_best_value(fields, name)
if value is not None:
if name == 'total_amount':
value = Decimal(value)
invoice_data[name] = value
invoice_data['lines'] = lines = []
for parsed_line in document.parsed_data.get('line_items', []):
lines.append(self._get_supplier_invoice_typless_line(
parsed_line, invoice_data))
invoice_data['taxes'] = taxes = []
for parsed_tax in document.parsed_data.get('vat_rates', []):
taxes.append(self._get_supplier_invoice_typless_tax(
parsed_tax, invoice_data))
return invoice_data
def _get_supplier_invoice_typless_line(self, parsed_line, invoice_data):
line = {}
if 'purchase_orders' in invoice_data:
line['purchase_orders'] = invoice_data['purchase_orders']
for name in [
'product_name', 'description', 'unit', 'quantity',
'unit_price', 'amount', 'purchase_order']:
value = get_best_value(parsed_line, name)
if value is not None:
if name in {'unit_price', 'amount'}:
value = Decimal(value)
elif name == 'quantity':
value = float(value)
line[name] = value
return line
def _get_supplier_invoice_typless_tax(sef, parsed_tax, invoice_data):
tax = {'type': 'percentage'}
percentage = get_best_value(parsed_tax, 'vat_rate_percentage')
if percentage is not None:
tax['rate'] = Decimal(percentage) / 100
net = get_best_value(parsed_tax, 'vat_rate_net')
if net is not None:
tax['base'] = Decimal(net)
return tax
@typless_api
def _send_feedback_typless(self, document):
payload = self._typless_feedback_payload(document)
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': self.typless_api_key,
}
response = requests.post(
ADD_DOCUMENT_FEEDBACK, json=payload, headers=headers)
response.raise_for_status()
def _typless_feedback_payload(self, document):
source = document.result
fields = document.parsed_data.get('extracted_fields', [])
learning_fields = []
getter = getattr(self, f'_typless_feedback_payload_{document.type}')
for field in fields:
name = field['name']
value = getter(source, name)
if value is None:
value = get_best_value(fields, name)
learning_fields.append({
'name': name,
'value': value,
})
payload = {
'document_type_name': self.typless_document_type,
'document_object_id': document.parsed_data['object_id'],
'learning_fields': learning_fields,
}
line_items = document.parsed_data.get('line_items')
if line_items is not None:
lines = []
getter = getattr(
self, f'_typless_feedback_payload_line_items_{document.type}')
for i, line_item in enumerate(line_items):
line = []
for field in line_item:
name = field['name']
value = getter(i, name, source)
if value is None:
value = get_best_value(line_item, name)
line.append({
'name': name,
'value': value,
})
lines.append(line)
payload['line_items'] = lines
vat_rates = document.parsed_data.get('vat_rates')
if vat_rates is not None:
taxes = []
getter = getattr(
self, f'_typless_feedback_payload_vat_rates_{document.type}')
for i, vat_rate in enumerate(vat_rates):
percentage = getter(i, 'vat_rate_percentage', source)
if percentage is None:
percentage = get_best_value(
vat_rate, 'vat_rate_percentage')
net = getter(i, 'vat_rate_net', source)
if net is None:
net = get_best_value(vat_rate, 'vat_rate_net')
taxes.append([{
'name': 'vat_rate_percentage',
'value': percentage,
}, {
'name': 'vat_rate_net',
'value': net,
}])
payload['vat_rates'] = taxes
return payload
def _typless_feedback_payload_document_incoming(self, document, name):
if name == 'document_type':
return document.type
def _typless_feedback_payload_line_items_document_incoming(
self, index, name, document):
pass
def _typless_feedback_payload_vat_rates_document_incoming(
self, index, name, document):
pass
def _typless_feedback_payload_supplier_invoice(self, invoice, name):
if name == 'company_name':
return invoice.company.party.name
elif name == 'company_tax_identifier':
if invoice.tax_identifier:
return invoice.tax_identifier.code
else:
return ''
elif name == 'supplier_name':
return invoice.party.name
elif name == 'tax_identifier':
if invoice.party_tax_identifier:
return invoice.party_tax_identifier.code
else:
return ''
elif name == 'currency':
return invoice.currency.code
elif name == 'number':
return invoice.reference
elif name == 'description':
return invoice.description
elif name == 'invoice_date':
return invoice.invoice_date.isoformat()
elif name == 'payment_term_date':
if invoice.payment_term_date:
return invoice.payment_term_date.isoformat()
else:
return ''
elif name == 'untaxed_amount':
return str(invoice.untaxed_amount)
elif name == 'tax_amount':
return str(invoice.tax_amount)
elif name == 'total_amount':
return str(invoice.total_amount)
elif name == 'purchase_orders':
return invoice.origins
def _typless_feedback_payload_line_items_supplier_invoice(
self, index, name, invoice):
lines = [l for l in invoice.lines if l.type == 'line']
try:
line = lines[index]
except IndexError:
return ''
if name == 'product_name':
if line.product:
return line.product.name
else:
return ''
elif name == 'description':
return line.description
elif name == 'unit':
if line.unit:
return line.unit.name
else:
return ''
elif name == 'quantity':
return str(line.quantity)
elif name == 'unit_price':
return str(line.unit_price)
elif name == 'amount':
return str(line.amount)
elif name == 'purchase_order':
return line.origin_name
def _typless_feedback_payload_vat_rates_supplier_invoice(
self, index, name, invoice):
try:
line = invoice.taxes[index]
except IndexError:
return ''
if name == 'vat_rate_percentage':
if line.tax and line.tax.type == 'percentage':
return str(line.tax.rate * 100)
else:
return ''
elif name == 'vat_rate_net':
return str(line.base)
@classmethod
def check_modification(cls, mode, services, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
super().check_modification(
mode, services, values=values, external=external)
if mode == 'write' and external and 'typless_api_key' in values:
warning_name = Warning.format('typless_credential', services)
if Warning.check(warning_name):
raise TyplessCredentialWarning(
warning_name,
gettext('document_incoming_ocr_typless'
'.msg_typless_credential_modified'))

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="document_incoming_ocr_service_view_form">
<field name="model">document.incoming.ocr.service</field>
<field name="inherit" ref="document_incoming_ocr.document_incoming_ocr_service_view_form"/>
<field name="name">document_incoming_ocr_service_form</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,12 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.exceptions import UserError, UserWarning
class TyplessError(UserError):
pass
class TyplessCredentialWarning(UserWarning):
pass

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,36 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr "Clau API"
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr "Tipus de document"
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr "El token estàndard de la pàgina de configuració de Typless."
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr "El nom del tipus de document al Typless."
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr "Estàs segur que vols modificar les credencials Typless?"
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
"La crida al webservice Typless ha fallat amb el següent missatge: \n"
"%(message)s"
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr "Typless"

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,36 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr "API-Key"
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr "Dokumententyp"
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr "Das Standard-Token von der Typeless Einstellungsseite."
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr "Der Name des Dokumententyps bei Typless."
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr "Wollen Sie wirklich die Typeless Anmeldedaten ändern?"
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
"Der Aufruf des Typless Webservices ist mit folgender Meldung fehlgeschlagen:\n"
"%(message)s"
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr "Typless"

View File

@@ -0,0 +1,36 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr "Clave API"
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr "Tipo de documento"
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr "El token estándar de la pagina de configuración de Typless."
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr "El nombre del tipo de documento en Typless."
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr "¿Está seguro de que desea modificar las credenciales de Typless?"
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
"La llamada al servicio web Typless ha fallado con el siguiente mensaje: \n"
"%(message)s"
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr "Typless"

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,38 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr "Clé API"
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr "Type de document"
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr "Le jeton standard de la page des paramètres Typless."
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr "Le nom du type de document sur Typless."
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
"Êtes-vous sûr de vouloir modifier les informations d'identification de "
"Typless ?"
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
"L'appel au service Web Typless a échoué avec le message d'erreur suivant :\n"
"%(message)s"
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr "Typless"

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,37 @@
#
#, fuzzy
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr "Chiave API"
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr "Tipo di documento"
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr "Il nome del tipo di documento su Typeless."
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
"La chiamata al servizio Web Typeless non è riuscita con il seguente messaggio di errore:\n"
"%(message)s"
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr "Typless"

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,36 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr "API sleutel"
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr "Soort document"
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr "De standaard token van de Typless configuratie pagina."
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr "De naam van het soort document op Typless."
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr "Weet u zeker dat u de inloggegevens van Typless wilt wijzigen?"
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
"Aanroepen van de Typless webservice is mislukt met de volgende foutmelding:\n"
"%(message)s"
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr "Typless"

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,35 @@
#
#, fuzzy
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr "API ključ"
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr "Vrsta dokumenta"
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr "Typeless"

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,34 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:document.incoming.ocr.service,typless_api_key:"
msgid "API Key"
msgstr ""
msgctxt "field:document.incoming.ocr.service,typless_document_type:"
msgid "Document Type"
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_api_key:"
msgid "The standard token from Typless settings page."
msgstr ""
msgctxt "help:document.incoming.ocr.service,typless_document_type:"
msgid "The name of the document type on Typless."
msgstr ""
msgctxt "model:ir.message,text:msg_typless_credential_modified"
msgid "Are you sure you want to modify Typless credentials?"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_typless_webserver_error"
msgid ""
"Typless webservice call failed with the following error message:\n"
"%(message)s"
msgstr ""
msgctxt "selection:document.incoming.ocr.service,type:"
msgid "Typless"
msgstr ""

View File

@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_typless_credential_modified">
<field name="text">Are you sure you want to modify Typless credentials?</field>
</record>
<record model="ir.message" id="msg_typless_webserver_error">
<field name="text">Typless webservice call failed with the following error message:
%(message)s</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,130 @@
======================================
Document Incoming OCR Typless Scenario
======================================
Imports::
>>> import os
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import (
... create_chart, create_fiscalyear, create_tax, get_accounts)
>>> from trytond.modules.account_invoice.tests.tools import (
... set_fiscalyear_invoice_sequences)
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
>>> from trytond.tools import file_open
Activate modules::
>>> config = activate_modules([
... 'document_incoming_ocr_typless',
... 'document_incoming_invoice'],
... create_company, create_chart)
>>> Document = Model.get('document.incoming')
>>> OCRService = Model.get('document.incoming.ocr.service')
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> UoM = Model.get('product.uom')
Get accounts::
>>> accounts = get_accounts()
Create taxes::
>>> tax_10 = create_tax(Decimal('0.1'))
>>> tax_10.save()
>>> tax_20 = create_tax(Decimal('0.2'))
>>> tax_20.save()
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
>>> fiscalyear.click('create_period')
Set default supplier::
>>> suppplier = Party(name="Saber")
>>> suppplier.save()
Create account category::
>>> account_category = ProductCategory(name="Accounting")
>>> account_category.accounting = True
>>> account_category.account_expense = accounts['expense']
>>> account_category.save()
Create product::
>>> hour, = UoM.find([('name', '=', "Hour")])
>>> template = ProductTemplate(name="Service")
>>> template.default_uom = hour
>>> template.type = 'service'
>>> template.account_category = account_category
>>> template.save()
>>> service, = template.products
Setup Typless service::
>>> ocr_service = OCRService(type='typless')
>>> ocr_service.typless_api_key = os.getenv('TYPLESS_API_KEY')
>>> ocr_service.typless_document_type = os.getenv('TYPLESS_DOCUMENT_TYPE')
>>> ocr_service.save()
Create incoming document::
>>> document = Document()
>>> document.name = 'invoice.pdf'
>>> with file_open(
... 'document_incoming_ocr_typless/tests/'
... 'supplier-invoice-sample.pdf',
... mode='rb') as fp:
... document.data = fp.read()
>>> document.type = 'supplier_invoice'
>>> document.save()
Process document::
>>> document.click('process')
>>> invoice = document.result
>>> assertEqual(invoice.party, suppplier)
>>> invoice.reference
'INV-0007'
>>> invoice.invoice_date
datetime.date(2023, 6, 28)
>>> invoice.payment_term_date
datetime.date(2023, 7, 28)
>>> len(invoice.lines)
2
>>> line_service, = [l for l in invoice.lines if l.product]
>>> assertEqual(line_service.product, service)
>>> line_service.quantity
23.0
>>> line_service.unit_price
Decimal('2.5000')
>>> line_goods, = [l for l in invoice.lines if not l.product]
>>> line_goods.quantity
40.0
>>> line_goods.unit_price
Decimal('5.0000')
>>> len(invoice.taxes)
2
>>> sorted([t.amount for t in invoice.taxes])
[Decimal('5.75'), Decimal('40.00')]
>>> sorted([t.base for t in invoice.taxes])
[Decimal('57.50'), Decimal('200.00')]
>>> assertEqual({t.tax for t in invoice.taxes}, {tax_10, tax_20})
>>> invoice.untaxed_amount
Decimal('257.50')
>>> invoice.tax_amount
Decimal('45.75')
>>> invoice.total_amount
Decimal('303.25')
Send feedback::
>>> document.click('ocr_send_feedback')

View File

@@ -0,0 +1,29 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
class DocumentIncomingOcrTyplessTestCase(ModuleTestCase):
"Test Document Incoming Ocr Typless module"
module = 'document_incoming_ocr_typless'
@with_transaction()
def test_service_match(self):
"Test Service match"
pool = Pool()
Service = pool.get('document.incoming.ocr.service')
for service, pattern, result in [
(Service(type='typless', source=None), {}, False),
(Service(type='typless', source=None),
{'mime_type': 'application/pdf'}, True),
(Service(type='typless', source=None),
{'mime_type': 'application/octet-stream'}, False),
]:
with self.subTest(service=service, pattern=pattern):
self.assertEqual(service.match(pattern), result)
del ModuleTestCase

View File

@@ -0,0 +1,15 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import os
from trytond.tests.test_tryton import TEST_NETWORK, load_doc_tests
def load_tests(*args, **kwargs):
if (not TEST_NETWORK
or not (os.getenv('TYPLESS_API_KEY')
and os.getenv('TYPLESS_DOCUMENT_TYPE'))):
kwargs.setdefault('skips', set()).add(
'scenario_document_incoming_ocr_typless.rst')
return load_doc_tests(__name__, __file__, *args, **kwargs)

View File

@@ -0,0 +1,17 @@
[tryton]
version=7.8.0
depends:
currency
document_incoming
document_incoming_ocr
ir
party
extras_depend:
document_incoming_invoice
xml:
document.xml
message.xml
[register]
model:
document.IncomingOCRService

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//separator[@id='criteria']" position="before">
<newline/>
<label name="typless_api_key"/>
<field name="typless_api_key" widget="password"/>
<label name="typless_document_type"/>
<field name="typless_document_type"/>
<newline/>
</xpath>
</data>