From 0aafdcd1df6f82b0538dff971a27d438b39aff58 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sun, 28 Aug 2016 00:24:41 +0200 Subject: [PATCH 01/33] Initial check-in of modules purchase_order_import, purchase_order_import_ubl and purchase_order_ubl Move XSD files to base_ubl, to avoid duplication of UBL XSD files. --- purchase_order_ubl/README.rst | 60 ++++++ purchase_order_ubl/__init__.py | 3 + purchase_order_ubl/__openerp__.py | 16 ++ purchase_order_ubl/models/__init__.py | 4 + purchase_order_ubl/models/purchase.py | 188 ++++++++++++++++++ purchase_order_ubl/models/report.py | 31 +++ purchase_order_ubl/tests/__init__.py | 3 + purchase_order_ubl/tests/test_ubl_generate.py | 29 +++ 8 files changed, 334 insertions(+) create mode 100644 purchase_order_ubl/README.rst create mode 100644 purchase_order_ubl/__init__.py create mode 100644 purchase_order_ubl/__openerp__.py create mode 100644 purchase_order_ubl/models/__init__.py create mode 100644 purchase_order_ubl/models/purchase.py create mode 100644 purchase_order_ubl/models/report.py create mode 100644 purchase_order_ubl/tests/__init__.py create mode 100644 purchase_order_ubl/tests/test_ubl_generate.py diff --git a/purchase_order_ubl/README.rst b/purchase_order_ubl/README.rst new file mode 100644 index 0000000000..0a5d2cda73 --- /dev/null +++ b/purchase_order_ubl/README.rst @@ -0,0 +1,60 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================== +Purchase Order UBL +================== + +This module adds support for UBL, the `Universal Business Language (UBL) `_ standard, on purchase orders. The UBL standard became the `ISO/IEC 19845 `_ standard in December 2015 (cf the `official announce _`). + +With this module, when you generate the purchase order or RFQ report: + +* on a draft/RFQ/Bid Received purchase order, the PDF file will have an embedded XML *Request For Quotation* file compliant with the UBL 2.1 standard. + +* on an approved purchase order, the PDF file will have an embedded XML *Order* file compliant with the UBL 2.1 standard. + +If your supplier has Odoo and has installed the module *sale_order_import_ubl*, he will be able to import the PDF file and it will automatically create the quotation/sale order. + +Configuration +============= + +No configuration is needed. + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/142/8.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Alexis de Lattre + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/purchase_order_ubl/__init__.py b/purchase_order_ubl/__init__.py new file mode 100644 index 0000000000..cde864bae2 --- /dev/null +++ b/purchase_order_ubl/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/purchase_order_ubl/__openerp__.py b/purchase_order_ubl/__openerp__.py new file mode 100644 index 0000000000..b26bec2072 --- /dev/null +++ b/purchase_order_ubl/__openerp__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# © 2016 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Purchase Order UBL', + 'version': '8.0.1.0.0', + 'category': 'Purchase Management', + 'license': 'AGPL-3', + 'summary': 'Embed UBL XML file inside the PDF purchase order', + 'author': 'Akretion,Odoo Community Association (OCA)', + 'website': 'http://www.akretion.com', + 'depends': ['purchase', 'base_ubl'], + 'data': [], + 'installable': True, +} diff --git a/purchase_order_ubl/models/__init__.py b/purchase_order_ubl/models/__init__.py new file mode 100644 index 0000000000..b2af320285 --- /dev/null +++ b/purchase_order_ubl/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import purchase +from . import report diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py new file mode 100644 index 0000000000..e884420cad --- /dev/null +++ b/purchase_order_ubl/models/purchase.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +# © 2016 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api +from lxml import etree +import logging + +logger = logging.getLogger(__name__) + + +class PurchaseOrder(models.Model): + _name = 'purchase.order' + _inherit = ['purchase.order', 'base.ubl'] + + @api.model + def get_rfq_states(self): + return ['draft', 'sent', 'bid'] + + @api.model + def get_order_states(self): + return ['approved', 'except_picking', 'except_invoice', 'done'] + + @api.multi + def _ubl_add_header(self, doc_type, parent_node, ns): + if doc_type == 'rfq': + now_utc = fields.Datetime.now() + date = now_utc[:10] + time = now_utc[11:] + currency_node_name = 'PricingCurrencyCode' + elif doc_type == 'order': + date = self.date_approve + currency_node_name = 'DocumentCurrencyCode' + ubl_version = etree.SubElement( + parent_node, ns['cbc'] + 'UBLVersionID') + ubl_version.text = '2.1' + doc_id = etree.SubElement(parent_node, ns['cbc'] + 'ID') + doc_id.text = self.name + issue_date = etree.SubElement(parent_node, ns['cbc'] + 'IssueDate') + issue_date.text = date + if doc_type == 'rfq': # IssueTime is required on RFQ, not on order + issue_time = etree.SubElement(parent_node, ns['cbc'] + 'IssueTime') + issue_time.text = time + if self.notes: + note = etree.SubElement(parent_node, ns['cbc'] + 'Note') + note.text = self.notes + doc_currency = etree.SubElement( + parent_node, ns['cbc'] + currency_node_name) + doc_currency.text = self.currency_id.name + + @api.multi + def _ubl_add_monetary_total(self, parent_node, ns): + monetary_total = etree.SubElement( + parent_node, ns['cac'] + 'AnticipatedMonetaryTotal') + line_total = etree.SubElement( + monetary_total, ns['cbc'] + 'LineExtensionAmount', + currencyID=self.currency_id.name) + line_total.text = unicode(self.amount_untaxed) + payable_amount = etree.SubElement( + monetary_total, ns['cbc'] + 'PayableAmount', + currencyID=self.currency_id.name) + payable_amount.text = unicode(self.amount_total) + + @api.multi + def _ubl_add_rfq_line(self, parent_node, oline, line_number, ns): + line_root = etree.SubElement( + parent_node, ns['cac'] + 'RequestForQuotationLine') + self._ubl_add_line_item( + line_number, oline.product_id, 'purchase', oline.product_qty, + oline.product_uom, line_root, ns, + seller=self.partner_id.commercial_partner_id) + + @api.multi + def _ubl_add_order_line(self, parent_node, oline, line_number, ns): + line_root = etree.SubElement( + parent_node, ns['cac'] + 'OrderLine') + dpo = self.env['decimal.precision'] + qty_precision = dpo.precision_get('Product Unit of Measure') + price_precision = dpo.precision_get('Product Price') + self._ubl_add_line_item( + line_number, oline.product_id, 'purchase', oline.product_qty, + oline.product_uom, line_root, ns, + seller=self.partner_id.commercial_partner_id, + currency=self.currency_id, price_subtotal=oline.price_subtotal, + qty_precision=qty_precision, price_precision=price_precision) + + @api.multi + def get_delivery_partner(self): + self.ensure_one() + if self.location_id.usage == 'customer': + partner = self.dest_address_id + else: + partner = self.location_id.partner_id or self.company_id.partner_id + return partner + + @api.multi + def generate_rfq_ubl_xml_etree(self): + nsmap, ns = self._ubl_get_nsmap_namespace('RequestForQuotation-2') + xml_root = etree.Element('RequestForQuotation', nsmap=nsmap) + doc_type = 'rfq' + self._ubl_add_header(doc_type, xml_root, ns) + + # The order of SellerSupplierParty / BuyerCustomerParty is different + # between RFQ and Order ! + self._ubl_add_supplier_party( + self.partner_id, False, 'SellerSupplierParty', xml_root, ns) + self._ubl_add_customer_party( + False, self.company_id, 'BuyerCustomerParty', xml_root, ns) + delivery_partner = self.get_delivery_partner() + self._ubl_add_delivery(delivery_partner, xml_root, ns) + if self.incoterm_id: + self._ubl_add_delivery_terms(self.incoterm_id, xml_root, ns) + + line_number = 0 + for oline in self.order_line: + line_number += 1 + self._ubl_add_rfq_line(xml_root, oline, line_number, ns) + return xml_root + + @api.multi + def generate_order_ubl_xml_etree(self): + nsmap, ns = self._ubl_get_nsmap_namespace('Order-2') + xml_root = etree.Element('Order', nsmap=nsmap) + doc_type = 'order' + self._ubl_add_header(doc_type, xml_root, ns) + + self._ubl_add_customer_party( + False, self.company_id, 'BuyerCustomerParty', xml_root, ns) + self._ubl_add_supplier_party( + self.partner_id, False, 'SellerSupplierParty', xml_root, ns) + delivery_partner = self.get_delivery_partner() + self._ubl_add_delivery(delivery_partner, xml_root, ns) + if self.incoterm_id: + self._ubl_add_delivery_terms(self.incoterm_id, xml_root, ns) + if self.payment_term_id: + self._ubl_add_payment_terms(self.payment_term_id, xml_root, ns) + self._ubl_add_monetary_total(xml_root, ns) + + line_number = 0 + for oline in self.order_line: + line_number += 1 + self._ubl_add_order_line(xml_root, oline, line_number, ns) + return xml_root + + @api.multi + def generate_ubl_xml_string(self, doc_type): + self.ensure_one() + assert doc_type in ('order', 'rfq'), 'wrong doc_type' + logger.debug('Starting to generate UBL XML %s file', doc_type) + if doc_type == 'order': + xml_root = self.generate_order_ubl_xml_etree() + xsd_filename = 'UBL-Order-2.1.xsd' + elif doc_type == 'rfq': + xml_root = self.generate_rfq_ubl_xml_etree() + xsd_filename = 'UBL-RequestForQuotation-2.1.xsd' + xml_string = etree.tostring( + xml_root, pretty_print=True, encoding='UTF-8', + xml_declaration=True) + self._check_xml_schema( + xml_string, 'base_ubl/data/xsd-2.1/maindoc/' + xsd_filename) + logger.debug( + '%s UBL XML file generated for purchase order ID %d (state %s)', + doc_type, self.id, self.state) + logger.debug(xml_string) + return xml_string + + @api.multi + def get_ubl_filename(self, doc_type): + """This method is designed to be inherited""" + if doc_type == 'rfq': + return 'UBL-RequestForQuotation-2.1.xml' + elif doc_type == 'order': + return 'UBL-Order-2.1.xml' + + @api.multi + def embed_ubl_xml_in_pdf(self, pdf_content): + self.ensure_one() + doc_type = False + if self.state in self.get_rfq_states(): + doc_type = 'rfq' + elif self.state in self.get_order_states(): + doc_type = 'order' + if doc_type: + ubl_filename = self.get_ubl_filename(doc_type) + xml_string = self.generate_ubl_xml_string(doc_type) + pdf_content = self.embed_xml_in_pdf( + xml_string, ubl_filename, pdf_content) + return pdf_content diff --git a/purchase_order_ubl/models/report.py b/purchase_order_ubl/models/report.py new file mode 100644 index 0000000000..24e461a118 --- /dev/null +++ b/purchase_order_ubl/models/report.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# © 2016 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api +import logging + +logger = logging.getLogger(__name__) + + +class Report(models.Model): + _inherit = 'report' + + @api.v7 + def get_pdf( + self, cr, uid, ids, report_name, html=None, data=None, + context=None): + """We go through that method when the PDF is generated for the 1st + time and also when it is read from the attachment. + This method is specific to QWeb""" + pdf_content = super(Report, self).get_pdf( + cr, uid, ids, report_name, html=html, data=data, context=context) + purchase_reports = [ + 'purchase.report_purchaseorder', + 'purchase.report_purchasequotation'] + if report_name in purchase_reports and len(ids) == 1: + order = self.pool['purchase.order'].browse( + cr, uid, ids[0], context=context) + pdf_content = order.embed_ubl_xml_in_pdf( + pdf_content) + return pdf_content diff --git a/purchase_order_ubl/tests/__init__.py b/purchase_order_ubl/tests/__init__.py new file mode 100644 index 0000000000..99dcf3142e --- /dev/null +++ b/purchase_order_ubl/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_ubl_generate diff --git a/purchase_order_ubl/tests/test_ubl_generate.py b/purchase_order_ubl/tests/test_ubl_generate.py new file mode 100644 index 0000000000..47e0dc7b34 --- /dev/null +++ b/purchase_order_ubl/tests/test_ubl_generate.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# © 2016 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp.tests.common import TransactionCase + + +class TestUblOrderImport(TransactionCase): + + def test_ubl_generate(self): + ro = self.registry['report'] + poo = self.env['purchase.order'] + buo = self.env['base.ubl'] + order_states = poo.get_order_states() + rfq_states = poo.get_rfq_states() + rfq_filename = poo.get_ubl_filename('rfq') + order_filename = poo.get_ubl_filename('order') + for i in range(6): + i += 1 + order = self.env.ref('purchase.purchase_order_%d' % i) + # I didn't manage to make it work with new api :-( + pdf_file = ro.get_pdf( + self.cr, self.uid, order.ids, + 'purchase.report_purchasequotation') + res = buo.get_xml_files_from_pdf(pdf_file) + if order.state in order_states: + self.assertTrue(order_filename in res) + elif order.state in rfq_states: + self.assertTrue(rfq_filename in res) From e8708e2922148eb722421ad8cb7710d47d3cc337 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sat, 10 Sep 2016 11:50:04 +0200 Subject: [PATCH 02/33] Call api.multi get_ubl_filename method on a real object --- purchase_order_ubl/tests/test_ubl_generate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/purchase_order_ubl/tests/test_ubl_generate.py b/purchase_order_ubl/tests/test_ubl_generate.py index 47e0dc7b34..34b068987c 100644 --- a/purchase_order_ubl/tests/test_ubl_generate.py +++ b/purchase_order_ubl/tests/test_ubl_generate.py @@ -5,7 +5,7 @@ from openerp.tests.common import TransactionCase -class TestUblOrderImport(TransactionCase): +class TestUblOrder(TransactionCase): def test_ubl_generate(self): ro = self.registry['report'] @@ -13,8 +13,6 @@ def test_ubl_generate(self): buo = self.env['base.ubl'] order_states = poo.get_order_states() rfq_states = poo.get_rfq_states() - rfq_filename = poo.get_ubl_filename('rfq') - order_filename = poo.get_ubl_filename('order') for i in range(6): i += 1 order = self.env.ref('purchase.purchase_order_%d' % i) @@ -24,6 +22,8 @@ def test_ubl_generate(self): 'purchase.report_purchasequotation') res = buo.get_xml_files_from_pdf(pdf_file) if order.state in order_states: - self.assertTrue(order_filename in res) + filename = order.get_ubl_filename('order') + self.assertTrue(filename in res) elif order.state in rfq_states: - self.assertTrue(rfq_filename in res) + filename = order.get_ubl_filename('rfq') + self.assertTrue(filename in res) From 995176f3456582ab302311fca1add1b5df6fc7e0 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sat, 10 Sep 2016 23:59:57 +0200 Subject: [PATCH 03/33] Update code following latest update of base_ubl --- purchase_order_ubl/models/purchase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index e884420cad..c4970c581f 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -78,8 +78,8 @@ def _ubl_add_order_line(self, parent_node, oline, line_number, ns): qty_precision = dpo.precision_get('Product Unit of Measure') price_precision = dpo.precision_get('Product Price') self._ubl_add_line_item( - line_number, oline.product_id, 'purchase', oline.product_qty, - oline.product_uom, line_root, ns, + line_number, oline.name, oline.product_id, 'purchase', + oline.product_qty, oline.product_uom, line_root, ns, seller=self.partner_id.commercial_partner_id, currency=self.currency_id, price_subtotal=oline.price_subtotal, qty_precision=qty_precision, price_precision=price_precision) From 7104bb961fc109b86e716098ffc98492ee83ade8 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 12 Sep 2016 01:58:12 +0200 Subject: [PATCH 04/33] Support context key no_embedded_ubl_xml --- purchase_order_ubl/models/report.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/purchase_order_ubl/models/report.py b/purchase_order_ubl/models/report.py index 24e461a118..0c6fb68b7c 100644 --- a/purchase_order_ubl/models/report.py +++ b/purchase_order_ubl/models/report.py @@ -18,12 +18,17 @@ def get_pdf( """We go through that method when the PDF is generated for the 1st time and also when it is read from the attachment. This method is specific to QWeb""" + if context is None: + context = {} pdf_content = super(Report, self).get_pdf( cr, uid, ids, report_name, html=html, data=data, context=context) purchase_reports = [ 'purchase.report_purchaseorder', 'purchase.report_purchasequotation'] - if report_name in purchase_reports and len(ids) == 1: + if ( + report_name in purchase_reports and + len(ids) == 1 and + not context.get('no_embedded_ubl_xml')): order = self.pool['purchase.order'].browse( cr, uid, ids[0], context=context) pdf_content = order.embed_ubl_xml_in_pdf( From 844464671bd39bd03c012621afc8aebc456bbb5b Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sat, 17 Sep 2016 22:44:01 +0200 Subject: [PATCH 05/33] Add support for UBL 2.0 --- purchase_order_ubl/README.rst | 6 +- purchase_order_ubl/models/purchase.py | 100 +++++++++++------- purchase_order_ubl/tests/test_ubl_generate.py | 24 +++-- 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/purchase_order_ubl/README.rst b/purchase_order_ubl/README.rst index 0a5d2cda73..4cf386007f 100644 --- a/purchase_order_ubl/README.rst +++ b/purchase_order_ubl/README.rst @@ -6,13 +6,13 @@ Purchase Order UBL ================== -This module adds support for UBL, the `Universal Business Language (UBL) `_ standard, on purchase orders. The UBL standard became the `ISO/IEC 19845 `_ standard in December 2015 (cf the `official announce _`). +This module adds support for UBL, the `Universal Business Language (UBL) `_ standard, on purchase orders. The UBL 2.1 standard became the `ISO/IEC 19845 `_ standard in December 2015 (cf the `official announce _`). With this module, when you generate the purchase order or RFQ report: -* on a draft/RFQ/Bid Received purchase order, the PDF file will have an embedded XML *Request For Quotation* file compliant with the UBL 2.1 standard. +* on a draft/RFQ/Bid Received purchase order, the PDF file will have an embedded XML *Request For Quotation* file compliant with the UBL 2.1 or 2.0 standard. -* on an approved purchase order, the PDF file will have an embedded XML *Order* file compliant with the UBL 2.1 standard. +* on an approved purchase order, the PDF file will have an embedded XML *Order* file compliant with the UBL 2.1 or 2.0 standard. If your supplier has Odoo and has installed the module *sale_order_import_ubl*, he will be able to import the PDF file and it will automatically create the quotation/sale order. diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index c4970c581f..7647f1048f 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -22,7 +22,7 @@ def get_order_states(self): return ['approved', 'except_picking', 'except_invoice', 'done'] @api.multi - def _ubl_add_header(self, doc_type, parent_node, ns): + def _ubl_add_header(self, doc_type, parent_node, ns, version='2.1'): if doc_type == 'rfq': now_utc = fields.Datetime.now() date = now_utc[:10] @@ -33,7 +33,7 @@ def _ubl_add_header(self, doc_type, parent_node, ns): currency_node_name = 'DocumentCurrencyCode' ubl_version = etree.SubElement( parent_node, ns['cbc'] + 'UBLVersionID') - ubl_version.text = '2.1' + ubl_version.text = version doc_id = etree.SubElement(parent_node, ns['cbc'] + 'ID') doc_id.text = self.name issue_date = etree.SubElement(parent_node, ns['cbc'] + 'IssueDate') @@ -49,7 +49,7 @@ def _ubl_add_header(self, doc_type, parent_node, ns): doc_currency.text = self.currency_id.name @api.multi - def _ubl_add_monetary_total(self, parent_node, ns): + def _ubl_add_monetary_total(self, parent_node, ns, version='2.1'): monetary_total = etree.SubElement( parent_node, ns['cac'] + 'AnticipatedMonetaryTotal') line_total = etree.SubElement( @@ -62,16 +62,18 @@ def _ubl_add_monetary_total(self, parent_node, ns): payable_amount.text = unicode(self.amount_total) @api.multi - def _ubl_add_rfq_line(self, parent_node, oline, line_number, ns): + def _ubl_add_rfq_line( + self, parent_node, oline, line_number, ns, version='2.1'): line_root = etree.SubElement( parent_node, ns['cac'] + 'RequestForQuotationLine') self._ubl_add_line_item( - line_number, oline.product_id, 'purchase', oline.product_qty, - oline.product_uom, line_root, ns, - seller=self.partner_id.commercial_partner_id) + line_number, oline.name, oline.product_id, 'purchase', + oline.product_qty, oline.product_uom, line_root, ns, + seller=self.partner_id.commercial_partner_id, version=version) @api.multi - def _ubl_add_order_line(self, parent_node, oline, line_number, ns): + def _ubl_add_order_line( + self, parent_node, oline, line_number, ns, version='2.1'): line_root = etree.SubElement( parent_node, ns['cac'] + 'OrderLine') dpo = self.env['decimal.precision'] @@ -82,7 +84,8 @@ def _ubl_add_order_line(self, parent_node, oline, line_number, ns): oline.product_qty, oline.product_uom, line_root, ns, seller=self.partner_id.commercial_partner_id, currency=self.currency_id, price_subtotal=oline.price_subtotal, - qty_precision=qty_precision, price_precision=price_precision) + qty_precision=qty_precision, price_precision=price_precision, + version=version) @api.multi def get_delivery_partner(self): @@ -94,70 +97,80 @@ def get_delivery_partner(self): return partner @api.multi - def generate_rfq_ubl_xml_etree(self): - nsmap, ns = self._ubl_get_nsmap_namespace('RequestForQuotation-2') + def generate_rfq_ubl_xml_etree(self, version='2.1'): + nsmap, ns = self._ubl_get_nsmap_namespace( + 'RequestForQuotation-2', version=version) xml_root = etree.Element('RequestForQuotation', nsmap=nsmap) doc_type = 'rfq' - self._ubl_add_header(doc_type, xml_root, ns) + self._ubl_add_header(doc_type, xml_root, ns, version=version) # The order of SellerSupplierParty / BuyerCustomerParty is different # between RFQ and Order ! self._ubl_add_supplier_party( - self.partner_id, False, 'SellerSupplierParty', xml_root, ns) - self._ubl_add_customer_party( - False, self.company_id, 'BuyerCustomerParty', xml_root, ns) + self.partner_id, False, 'SellerSupplierParty', xml_root, ns, + version=version) + if version == '2.1': + self._ubl_add_customer_party( + False, self.company_id, 'BuyerCustomerParty', xml_root, ns, + version=version) delivery_partner = self.get_delivery_partner() - self._ubl_add_delivery(delivery_partner, xml_root, ns) + self._ubl_add_delivery(delivery_partner, xml_root, ns, version=version) if self.incoterm_id: - self._ubl_add_delivery_terms(self.incoterm_id, xml_root, ns) + self._ubl_add_delivery_terms( + self.incoterm_id, xml_root, ns, version=version) line_number = 0 for oline in self.order_line: line_number += 1 - self._ubl_add_rfq_line(xml_root, oline, line_number, ns) + self._ubl_add_rfq_line( + xml_root, oline, line_number, ns, version=version) return xml_root @api.multi - def generate_order_ubl_xml_etree(self): - nsmap, ns = self._ubl_get_nsmap_namespace('Order-2') + def generate_order_ubl_xml_etree(self, version='2.1'): + nsmap, ns = self._ubl_get_nsmap_namespace('Order-2', version=version) xml_root = etree.Element('Order', nsmap=nsmap) doc_type = 'order' - self._ubl_add_header(doc_type, xml_root, ns) + self._ubl_add_header(doc_type, xml_root, ns, version=version) self._ubl_add_customer_party( - False, self.company_id, 'BuyerCustomerParty', xml_root, ns) + False, self.company_id, 'BuyerCustomerParty', xml_root, ns, + version=version) self._ubl_add_supplier_party( - self.partner_id, False, 'SellerSupplierParty', xml_root, ns) + self.partner_id, False, 'SellerSupplierParty', xml_root, ns, + version=version) delivery_partner = self.get_delivery_partner() - self._ubl_add_delivery(delivery_partner, xml_root, ns) + self._ubl_add_delivery(delivery_partner, xml_root, ns, version=version) if self.incoterm_id: - self._ubl_add_delivery_terms(self.incoterm_id, xml_root, ns) + self._ubl_add_delivery_terms( + self.incoterm_id, xml_root, ns, version=version) if self.payment_term_id: - self._ubl_add_payment_terms(self.payment_term_id, xml_root, ns) - self._ubl_add_monetary_total(xml_root, ns) + self._ubl_add_payment_terms( + self.payment_term_id, xml_root, ns, version=version) + self._ubl_add_monetary_total(xml_root, ns, version=version) line_number = 0 for oline in self.order_line: line_number += 1 - self._ubl_add_order_line(xml_root, oline, line_number, ns) + self._ubl_add_order_line( + xml_root, oline, line_number, ns, version=version) return xml_root @api.multi - def generate_ubl_xml_string(self, doc_type): + def generate_ubl_xml_string(self, doc_type, version='2.1'): self.ensure_one() assert doc_type in ('order', 'rfq'), 'wrong doc_type' logger.debug('Starting to generate UBL XML %s file', doc_type) if doc_type == 'order': - xml_root = self.generate_order_ubl_xml_etree() - xsd_filename = 'UBL-Order-2.1.xsd' + xml_root = self.generate_order_ubl_xml_etree(version=version) + document = 'Order' elif doc_type == 'rfq': - xml_root = self.generate_rfq_ubl_xml_etree() - xsd_filename = 'UBL-RequestForQuotation-2.1.xsd' + xml_root = self.generate_rfq_ubl_xml_etree(version=version) + document = 'RequestForQuotation' xml_string = etree.tostring( xml_root, pretty_print=True, encoding='UTF-8', xml_declaration=True) - self._check_xml_schema( - xml_string, 'base_ubl/data/xsd-2.1/maindoc/' + xsd_filename) + self._ubl_check_xml_schema(xml_string, document, version=version) logger.debug( '%s UBL XML file generated for purchase order ID %d (state %s)', doc_type, self.id, self.state) @@ -165,12 +178,17 @@ def generate_ubl_xml_string(self, doc_type): return xml_string @api.multi - def get_ubl_filename(self, doc_type): + def get_ubl_filename(self, doc_type, version='2.1'): """This method is designed to be inherited""" if doc_type == 'rfq': - return 'UBL-RequestForQuotation-2.1.xml' + return 'UBL-RequestForQuotation-%s.xml' % version elif doc_type == 'order': - return 'UBL-Order-2.1.xml' + return 'UBL-Order-%s.xml' % version + + @api.multi + def get_ubl_version(self): + version = self._context.get('ubl_version') or '2.1' + return version @api.multi def embed_ubl_xml_in_pdf(self, pdf_content): @@ -181,8 +199,10 @@ def embed_ubl_xml_in_pdf(self, pdf_content): elif self.state in self.get_order_states(): doc_type = 'order' if doc_type: - ubl_filename = self.get_ubl_filename(doc_type) - xml_string = self.generate_ubl_xml_string(doc_type) + version = self.get_ubl_version() + ubl_filename = self.get_ubl_filename(doc_type, version=version) + xml_string = self.generate_ubl_xml_string( + doc_type, version=version) pdf_content = self.embed_xml_in_pdf( xml_string, ubl_filename, pdf_content) return pdf_content diff --git a/purchase_order_ubl/tests/test_ubl_generate.py b/purchase_order_ubl/tests/test_ubl_generate.py index 34b068987c..e63bf559e5 100644 --- a/purchase_order_ubl/tests/test_ubl_generate.py +++ b/purchase_order_ubl/tests/test_ubl_generate.py @@ -16,14 +16,16 @@ def test_ubl_generate(self): for i in range(6): i += 1 order = self.env.ref('purchase.purchase_order_%d' % i) - # I didn't manage to make it work with new api :-( - pdf_file = ro.get_pdf( - self.cr, self.uid, order.ids, - 'purchase.report_purchasequotation') - res = buo.get_xml_files_from_pdf(pdf_file) - if order.state in order_states: - filename = order.get_ubl_filename('order') - self.assertTrue(filename in res) - elif order.state in rfq_states: - filename = order.get_ubl_filename('rfq') - self.assertTrue(filename in res) + for version in ['2.0', '2.1']: + # I didn't manage to make it work with new api :-( + pdf_file = ro.get_pdf( + self.cr, self.uid, order.ids, + 'purchase.report_purchasequotation', + context={'ubl_version': version}) + res = buo.get_xml_files_from_pdf(pdf_file) + if order.state in order_states: + filename = order.get_ubl_filename('order', version=version) + self.assertTrue(filename in res) + elif order.state in rfq_states: + filename = order.get_ubl_filename('rfq', version=version) + self.assertTrue(filename in res) From d23f4dc9d43b2c00f6af11f70b06accd1f59bfaf Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 19 Sep 2016 22:38:34 +0200 Subject: [PATCH 06/33] Handle lang in UBL XML file generation --- purchase_order_ubl/models/purchase.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index 7647f1048f..20db925d95 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -161,11 +161,20 @@ def generate_ubl_xml_string(self, doc_type, version='2.1'): self.ensure_one() assert doc_type in ('order', 'rfq'), 'wrong doc_type' logger.debug('Starting to generate UBL XML %s file', doc_type) + lang = self.get_ubl_lang() + # The aim of injecting lang in context + # is to have the content of the XML in the partner's lang + # but the problem is that the error messages will also be in + # that lang. But the error messages should almost never + # happen except the first days of use, so it's probably + # not worth the additionnal code to handle the 2 langs if doc_type == 'order': - xml_root = self.generate_order_ubl_xml_etree(version=version) + xml_root = self.with_context(lang=lang).\ + generate_order_ubl_xml_etree(version=version) document = 'Order' elif doc_type == 'rfq': - xml_root = self.generate_rfq_ubl_xml_etree(version=version) + xml_root = self.with_context(lang=lang).\ + generate_rfq_ubl_xml_etree(version=version) document = 'RequestForQuotation' xml_string = etree.tostring( xml_root, pretty_print=True, encoding='UTF-8', @@ -190,6 +199,10 @@ def get_ubl_version(self): version = self._context.get('ubl_version') or '2.1' return version + @api.multi + def get_ubl_lang(self): + return self.partner_id.lang or 'en_US' + @api.multi def embed_ubl_xml_in_pdf(self, pdf_content): self.ensure_one() From 5308307256b6dec0c588bbcc7f0cec262f14c773 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 18 Oct 2016 23:02:55 +0200 Subject: [PATCH 07/33] 8.0 Add support for partner bank matching on invoice update (#6) Add support for partner bank matching on invoice update (before, it was only supported on invoice creation) --- purchase_order_ubl/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/purchase_order_ubl/README.rst b/purchase_order_ubl/README.rst index 4cf386007f..96a1fa921b 100644 --- a/purchase_order_ubl/README.rst +++ b/purchase_order_ubl/README.rst @@ -26,13 +26,13 @@ Usage .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/142/8.0 + :target: https://runbot.odoo-community.org/runbot/226/8.0 Bug Tracker =========== Bugs are tracked on `GitHub Issues -`_. In case of trouble, please +`_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. From da326d5fefe514d331c06e32ccdd8edc46026676 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 15 Feb 2017 15:11:22 +0100 Subject: [PATCH 08/33] Port purchase_order_ubl to v10 Prepare v10 branch Rename __openerp__.py to __manifest__.py and set installable to False Also port all the modules that generate the XML documents: account_invoice_ubl, account_invoice_zugferd, purchase_order_ubl and sale_order_ubl Rename account_invoice_zugferd to account_invoice_factur-x Rename account_invoice_import_zugferd to account_invoice_import_factur-x Add module to support py3o reporting engine: Continue port of modules for v10.0, in particular sale_order_import_* module Fix spelling mistake and other remarks on README by Tarteo Disable get_pdf() in all tests because it doesn't work in Travis [10.0] restore get_pdf() in tests (#31) * sale_order_ubl + purchase_order_ubl: restore get_pdf() in tests using HttpCase * Restore get_pdf() in tests of account_invoice_factur-x and account_invoice_ubl modules * Update oca_dependencies.txt --- purchase_order_ubl/README.rst | 2 +- .../{__openerp__.py => __manifest__.py} | 4 +-- purchase_order_ubl/models/purchase.py | 25 +++++++++++-------- purchase_order_ubl/models/report.py | 24 +++++++----------- purchase_order_ubl/tests/test_ubl_generate.py | 20 +++++++-------- 5 files changed, 36 insertions(+), 39 deletions(-) rename purchase_order_ubl/{__openerp__.py => __manifest__.py} (81%) diff --git a/purchase_order_ubl/README.rst b/purchase_order_ubl/README.rst index 96a1fa921b..84121b3a1e 100644 --- a/purchase_order_ubl/README.rst +++ b/purchase_order_ubl/README.rst @@ -26,7 +26,7 @@ Usage .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/226/8.0 + :target: https://runbot.odoo-community.org/runbot/226/10.0 Bug Tracker =========== diff --git a/purchase_order_ubl/__openerp__.py b/purchase_order_ubl/__manifest__.py similarity index 81% rename from purchase_order_ubl/__openerp__.py rename to purchase_order_ubl/__manifest__.py index b26bec2072..3750658f61 100644 --- a/purchase_order_ubl/__openerp__.py +++ b/purchase_order_ubl/__manifest__.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -# © 2016 Akretion (Alexis de Lattre ) +# © 2016-2017 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'Purchase Order UBL', - 'version': '8.0.1.0.0', + 'version': '10.0.1.0.0', 'category': 'Purchase Management', 'license': 'AGPL-3', 'summary': 'Embed UBL XML file inside the PDF purchase order', diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index 20db925d95..2cf8b83677 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# © 2016 Akretion (Alexis de Lattre ) +# © 2016-2017 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api +from odoo import models, fields, api from lxml import etree import logging @@ -15,11 +15,11 @@ class PurchaseOrder(models.Model): @api.model def get_rfq_states(self): - return ['draft', 'sent', 'bid'] + return ['draft', 'sent', 'to approve'] @api.model def get_order_states(self): - return ['approved', 'except_picking', 'except_invoice', 'done'] + return ['purchase', 'done'] @api.multi def _ubl_add_header(self, doc_type, parent_node, ns, version='2.1'): @@ -29,7 +29,7 @@ def _ubl_add_header(self, doc_type, parent_node, ns, version='2.1'): time = now_utc[11:] currency_node_name = 'PricingCurrencyCode' elif doc_type == 'order': - date = self.date_approve + date = self.date_approve or self.date_order[:10] currency_node_name = 'DocumentCurrencyCode' ubl_version = etree.SubElement( parent_node, ns['cbc'] + 'UBLVersionID') @@ -90,10 +90,14 @@ def _ubl_add_order_line( @api.multi def get_delivery_partner(self): self.ensure_one() - if self.location_id.usage == 'customer': + if self.dest_address_id: partner = self.dest_address_id + elif ( + self.picking_type_id.warehouse_id and + self.picking_type_id.warehouse_id.partner_id): + partner = self.picking_type_id.warehouse_id.partner_id else: - partner = self.location_id.partner_id or self.company_id.partner_id + partner = self.company_id.partner_id return partner @api.multi @@ -167,7 +171,7 @@ def generate_ubl_xml_string(self, doc_type, version='2.1'): # but the problem is that the error messages will also be in # that lang. But the error messages should almost never # happen except the first days of use, so it's probably - # not worth the additionnal code to handle the 2 langs + # not worth the additional code to handle the 2 langs if doc_type == 'order': xml_root = self.with_context(lang=lang).\ generate_order_ubl_xml_etree(version=version) @@ -204,7 +208,7 @@ def get_ubl_lang(self): return self.partner_id.lang or 'en_US' @api.multi - def embed_ubl_xml_in_pdf(self, pdf_content): + def embed_ubl_xml_in_pdf(self, pdf_content=None, pdf_file=None): self.ensure_one() doc_type = False if self.state in self.get_rfq_states(): @@ -217,5 +221,6 @@ def embed_ubl_xml_in_pdf(self, pdf_content): xml_string = self.generate_ubl_xml_string( doc_type, version=version) pdf_content = self.embed_xml_in_pdf( - xml_string, ubl_filename, pdf_content) + xml_string, ubl_filename, + pdf_content=pdf_content, pdf_file=pdf_file) return pdf_content diff --git a/purchase_order_ubl/models/report.py b/purchase_order_ubl/models/report.py index 0c6fb68b7c..554fdab7b7 100644 --- a/purchase_order_ubl/models/report.py +++ b/purchase_order_ubl/models/report.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# © 2016 Akretion (Alexis de Lattre ) +# © 2016-2017 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import models, api +from odoo import models, api import logging logger = logging.getLogger(__name__) @@ -11,26 +11,20 @@ class Report(models.Model): _inherit = 'report' - @api.v7 - def get_pdf( - self, cr, uid, ids, report_name, html=None, data=None, - context=None): + @api.model + def get_pdf(self, docids, report_name, html=None, data=None): """We go through that method when the PDF is generated for the 1st time and also when it is read from the attachment. This method is specific to QWeb""" - if context is None: - context = {} pdf_content = super(Report, self).get_pdf( - cr, uid, ids, report_name, html=html, data=data, context=context) + docids, report_name, html=html, data=data) purchase_reports = [ 'purchase.report_purchaseorder', 'purchase.report_purchasequotation'] if ( report_name in purchase_reports and - len(ids) == 1 and - not context.get('no_embedded_ubl_xml')): - order = self.pool['purchase.order'].browse( - cr, uid, ids[0], context=context) - pdf_content = order.embed_ubl_xml_in_pdf( - pdf_content) + len(docids) == 1 and + not self._context.get('no_embedded_ubl_xml')): + order = self.env['purchase.order'].browse(docids[0]) + pdf_content = order.embed_ubl_xml_in_pdf(pdf_content=pdf_content) return pdf_content diff --git a/purchase_order_ubl/tests/test_ubl_generate.py b/purchase_order_ubl/tests/test_ubl_generate.py index e63bf559e5..f87da187db 100644 --- a/purchase_order_ubl/tests/test_ubl_generate.py +++ b/purchase_order_ubl/tests/test_ubl_generate.py @@ -1,30 +1,28 @@ # -*- coding: utf-8 -*- -# © 2016 Akretion (Alexis de Lattre ) +# © 2016-2017 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.tests.common import TransactionCase +from odoo.tests.common import HttpCase -class TestUblOrder(TransactionCase): +class TestUblOrder(HttpCase): def test_ubl_generate(self): - ro = self.registry['report'] + ro = self.env['report'] poo = self.env['purchase.order'] buo = self.env['base.ubl'] order_states = poo.get_order_states() rfq_states = poo.get_rfq_states() - for i in range(6): + for i in range(7): i += 1 order = self.env.ref('purchase.purchase_order_%d' % i) for version in ['2.0', '2.1']: - # I didn't manage to make it work with new api :-( - pdf_file = ro.get_pdf( - self.cr, self.uid, order.ids, - 'purchase.report_purchasequotation', - context={'ubl_version': version}) + pdf_file = ro.with_context(ubl_version=version).get_pdf( + order.ids, 'purchase.report_purchasequotation') res = buo.get_xml_files_from_pdf(pdf_file) if order.state in order_states: - filename = order.get_ubl_filename('order', version=version) + filename = order.get_ubl_filename( + 'order', version=version) self.assertTrue(filename in res) elif order.state in rfq_states: filename = order.get_ubl_filename('rfq', version=version) From 7e5e5dabdafa17a2b70920fe1a05ef8948570fa2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 13 Feb 2018 09:14:05 +0100 Subject: [PATCH 09/33] [11.0][MIG] purchase_order_ubl --- purchase_order_ubl/README.rst | 20 ++++++++-------- purchase_order_ubl/__init__.py | 2 +- purchase_order_ubl/__manifest__.py | 7 +++--- purchase_order_ubl/models/__init__.py | 2 +- purchase_order_ubl/models/purchase.py | 7 +++--- purchase_order_ubl/models/report.py | 24 ++++++++----------- purchase_order_ubl/readme/CONTRIBUTORS.rst | 2 ++ purchase_order_ubl/readme/DESCRIPTION.rst | 12 ++++++++++ purchase_order_ubl/tests/__init__.py | 2 +- purchase_order_ubl/tests/test_ubl_generate.py | 10 ++++---- 10 files changed, 48 insertions(+), 40 deletions(-) create mode 100644 purchase_order_ubl/readme/CONTRIBUTORS.rst create mode 100644 purchase_order_ubl/readme/DESCRIPTION.rst diff --git a/purchase_order_ubl/README.rst b/purchase_order_ubl/README.rst index 84121b3a1e..38cbb5e7ee 100644 --- a/purchase_order_ubl/README.rst +++ b/purchase_order_ubl/README.rst @@ -1,12 +1,15 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl :alt: License: AGPL-3 ================== Purchase Order UBL ================== -This module adds support for UBL, the `Universal Business Language (UBL) `_ standard, on purchase orders. The UBL 2.1 standard became the `ISO/IEC 19845 `_ standard in December 2015 (cf the `official announce _`). +This module adds support for UBL, the `Universal Business Language (UBL) `_ standard, +on purchase orders. The UBL 2.1 standard became the +`ISO/IEC 19845 `_ standard +in December 2015 (cf the `official announce `_). With this module, when you generate the purchase order or RFQ report: @@ -16,25 +19,21 @@ With this module, when you generate the purchase order or RFQ report: If your supplier has Odoo and has installed the module *sale_order_import_ubl*, he will be able to import the PDF file and it will automatically create the quotation/sale order. -Configuration -============= - -No configuration is needed. - Usage ===== .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/226/10.0 + :target: https://runbot.odoo-community.org/runbot/226/11.0 Bug Tracker =========== + Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, -help us smashing it by providing a detailed and welcomed feedback. +help us smash it by providing detailed and welcomed feedback. Credits ======= @@ -43,6 +42,7 @@ Contributors ------------ * Alexis de Lattre +* Andrea Stirpe Maintainer ---------- diff --git a/purchase_order_ubl/__init__.py b/purchase_order_ubl/__init__.py index cde864bae2..31660d6a96 100644 --- a/purchase_order_ubl/__init__.py +++ b/purchase_order_ubl/__init__.py @@ -1,3 +1,3 @@ -# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import models diff --git a/purchase_order_ubl/__manifest__.py b/purchase_order_ubl/__manifest__.py index 3750658f61..9854cfb19c 100644 --- a/purchase_order_ubl/__manifest__.py +++ b/purchase_order_ubl/__manifest__.py @@ -1,15 +1,14 @@ -# -*- coding: utf-8 -*- # © 2016-2017 Akretion (Alexis de Lattre ) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { 'name': 'Purchase Order UBL', - 'version': '10.0.1.0.0', + 'version': '11.0.1.0.0', 'category': 'Purchase Management', 'license': 'AGPL-3', 'summary': 'Embed UBL XML file inside the PDF purchase order', 'author': 'Akretion,Odoo Community Association (OCA)', - 'website': 'http://www.akretion.com', + 'website': 'https://github.com/OCA/edi/', 'depends': ['purchase', 'base_ubl'], 'data': [], 'installable': True, diff --git a/purchase_order_ubl/models/__init__.py b/purchase_order_ubl/models/__init__.py index b2af320285..0dbc3142e1 100644 --- a/purchase_order_ubl/models/__init__.py +++ b/purchase_order_ubl/models/__init__.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import purchase from . import report diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index 2cf8b83677..0a00d233f9 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- # © 2016-2017 Akretion (Alexis de Lattre ) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import models, fields, api from lxml import etree @@ -55,11 +54,11 @@ def _ubl_add_monetary_total(self, parent_node, ns, version='2.1'): line_total = etree.SubElement( monetary_total, ns['cbc'] + 'LineExtensionAmount', currencyID=self.currency_id.name) - line_total.text = unicode(self.amount_untaxed) + line_total.text = str(self.amount_untaxed) payable_amount = etree.SubElement( monetary_total, ns['cbc'] + 'PayableAmount', currencyID=self.currency_id.name) - payable_amount.text = unicode(self.amount_total) + payable_amount.text = str(self.amount_total) @api.multi def _ubl_add_rfq_line( diff --git a/purchase_order_ubl/models/report.py b/purchase_order_ubl/models/report.py index 554fdab7b7..5a48c16ddc 100644 --- a/purchase_order_ubl/models/report.py +++ b/purchase_order_ubl/models/report.py @@ -1,30 +1,26 @@ -# -*- coding: utf-8 -*- # © 2016-2017 Akretion (Alexis de Lattre ) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import models, api -import logging - -logger = logging.getLogger(__name__) +from odoo import api, models class Report(models.Model): - _inherit = 'report' + _inherit = "ir.actions.report" - @api.model - def get_pdf(self, docids, report_name, html=None, data=None): + @api.multi + def render_qweb_pdf(self, res_ids=None, data=None): """We go through that method when the PDF is generated for the 1st time and also when it is read from the attachment. This method is specific to QWeb""" - pdf_content = super(Report, self).get_pdf( - docids, report_name, html=html, data=data) + pdf_content = super(Report, self).render_qweb_pdf(res_ids, data) purchase_reports = [ 'purchase.report_purchaseorder', 'purchase.report_purchasequotation'] if ( - report_name in purchase_reports and - len(docids) == 1 and + len(self) == 1 and + self.report_name in purchase_reports and + len(res_ids) == 1 and not self._context.get('no_embedded_ubl_xml')): - order = self.env['purchase.order'].browse(docids[0]) + order = self.env['purchase.order'].browse(res_ids[0]) pdf_content = order.embed_ubl_xml_in_pdf(pdf_content=pdf_content) return pdf_content diff --git a/purchase_order_ubl/readme/CONTRIBUTORS.rst b/purchase_order_ubl/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..1befc12101 --- /dev/null +++ b/purchase_order_ubl/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Alexis de Lattre +* Andrea Stirpe diff --git a/purchase_order_ubl/readme/DESCRIPTION.rst b/purchase_order_ubl/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..5b03aa7b56 --- /dev/null +++ b/purchase_order_ubl/readme/DESCRIPTION.rst @@ -0,0 +1,12 @@ +This module adds support for UBL, the `Universal Business Language (UBL) `_ standard, +on purchase orders. The UBL 2.1 standard became the +`ISO/IEC 19845 `_ standard +in December 2015 (cf the `official announce `_). + +With this module, when you generate the purchase order or RFQ report: + +* on a draft/RFQ/Bid Received purchase order, the PDF file will have an embedded XML *Request For Quotation* file compliant with the UBL 2.1 or 2.0 standard. + +* on an approved purchase order, the PDF file will have an embedded XML *Order* file compliant with the UBL 2.1 or 2.0 standard. + +If your supplier has Odoo and has installed the module *sale_order_import_ubl*, he will be able to import the PDF file and it will automatically create the quotation/sale order. diff --git a/purchase_order_ubl/tests/__init__.py b/purchase_order_ubl/tests/__init__.py index 99dcf3142e..895163754b 100644 --- a/purchase_order_ubl/tests/__init__.py +++ b/purchase_order_ubl/tests/__init__.py @@ -1,3 +1,3 @@ -# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import test_ubl_generate diff --git a/purchase_order_ubl/tests/test_ubl_generate.py b/purchase_order_ubl/tests/test_ubl_generate.py index f87da187db..5662ef94b5 100644 --- a/purchase_order_ubl/tests/test_ubl_generate.py +++ b/purchase_order_ubl/tests/test_ubl_generate.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- # © 2016-2017 Akretion (Alexis de Lattre ) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo.tests.common import HttpCase @@ -8,7 +7,7 @@ class TestUblOrder(HttpCase): def test_ubl_generate(self): - ro = self.env['report'] + ro = self.env.ref('purchase.report_purchase_quotation') poo = self.env['purchase.order'] buo = self.env['base.ubl'] order_states = poo.get_order_states() @@ -17,8 +16,9 @@ def test_ubl_generate(self): i += 1 order = self.env.ref('purchase.purchase_order_%d' % i) for version in ['2.0', '2.1']: - pdf_file = ro.with_context(ubl_version=version).get_pdf( - order.ids, 'purchase.report_purchasequotation') + pdf_file = ro.with_context( + ubl_version=version + ).render_qweb_pdf(order.ids)[0] res = buo.get_xml_files_from_pdf(pdf_file) if order.state in order_states: filename = order.get_ubl_filename( From 5ac8f2b97e610d4c70dd60e40b22d112d4af0da4 Mon Sep 17 00:00:00 2001 From: "luc.demeyer@noviat.com" Date: Mon, 29 Oct 2018 15:53:15 +0100 Subject: [PATCH 10/33] [FIX]support custom reports --- purchase_order_ubl/models/report.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/purchase_order_ubl/models/report.py b/purchase_order_ubl/models/report.py index 5a48c16ddc..76af6ea015 100644 --- a/purchase_order_ubl/models/report.py +++ b/purchase_order_ubl/models/report.py @@ -13,9 +13,7 @@ def render_qweb_pdf(self, res_ids=None, data=None): time and also when it is read from the attachment. This method is specific to QWeb""" pdf_content = super(Report, self).render_qweb_pdf(res_ids, data) - purchase_reports = [ - 'purchase.report_purchaseorder', - 'purchase.report_purchasequotation'] + purchase_reports = self._get_purchase_order_ubl_reports() if ( len(self) == 1 and self.report_name in purchase_reports and @@ -24,3 +22,8 @@ def render_qweb_pdf(self, res_ids=None, data=None): order = self.env['purchase.order'].browse(res_ids[0]) pdf_content = order.embed_ubl_xml_in_pdf(pdf_content=pdf_content) return pdf_content + + def _get_purchase_order_ubl_reports(self): + return [ + 'purchase.report_purchaseorder', + 'purchase.report_purchasequotation'] From f3c9605ac1ad24a3612837788e5360bda356570f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 31 Oct 2018 08:54:13 +0000 Subject: [PATCH 11/33] [UPD] README.rst --- purchase_order_ubl/README.rst | 63 ++- .../i18n/purchase_order_ubl.pot | 25 + .../static/description/index.html | 429 ++++++++++++++++++ 3 files changed, 498 insertions(+), 19 deletions(-) create mode 100644 purchase_order_ubl/i18n/purchase_order_ubl.pot create mode 100644 purchase_order_ubl/static/description/index.html diff --git a/purchase_order_ubl/README.rst b/purchase_order_ubl/README.rst index 38cbb5e7ee..3d3f1f1a1c 100644 --- a/purchase_order_ubl/README.rst +++ b/purchase_order_ubl/README.rst @@ -1,11 +1,30 @@ -.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png - :target: https://www.gnu.org/licenses/agpl - :alt: License: AGPL-3 - ================== Purchase Order UBL ================== +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github + :target: https://github.com/OCA/edi/tree/11.0/purchase_order_ubl + :alt: OCA/edi +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-11-0/edi-11-0-purchase_order_ubl + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/226/11.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + This module adds support for UBL, the `Universal Business Language (UBL) `_ standard, on purchase orders. The UBL 2.1 standard became the `ISO/IEC 19845 `_ standard @@ -19,42 +38,48 @@ With this module, when you generate the purchase order or RFQ report: If your supplier has Odoo and has installed the module *sale_order_import_ubl*, he will be able to import the PDF file and it will automatically create the quotation/sale order. -Usage -===== +**Table of contents** -.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas - :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/226/11.0 +.. contents:: + :local: Bug Tracker =========== +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. -Bugs are tracked on `GitHub Issues -`_. In case of trouble, please -check there if your issue has already been reported. If you spotted it first, -help us smash it by providing detailed and welcomed feedback. +Do not contact contributors directly about support or help with technical issues. Credits ======= +Authors +~~~~~~~ + +* Akretion + Contributors ------------- +~~~~~~~~~~~~ * Alexis de Lattre * Andrea Stirpe -Maintainer ----------- +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. .. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association :target: https://odoo-community.org -This module is maintained by the OCA. - OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -To contribute to this module, please visit https://odoo-community.org. +This module is part of the `OCA/edi `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/purchase_order_ubl/i18n/purchase_order_ubl.pot b/purchase_order_ubl/i18n/purchase_order_ubl.pot new file mode 100644 index 0000000000..471e5815ad --- /dev/null +++ b/purchase_order_ubl/i18n/purchase_order_ubl.pot @@ -0,0 +1,25 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_order_ubl +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: purchase_order_ubl +#: model:ir.model,name:purchase_order_ubl.model_purchase_order +msgid "Purchase Order" +msgstr "" + +#. module: purchase_order_ubl +#: model:ir.model,name:purchase_order_ubl.model_ir_actions_report +msgid "ir.actions.report" +msgstr "" + diff --git a/purchase_order_ubl/static/description/index.html b/purchase_order_ubl/static/description/index.html new file mode 100644 index 0000000000..d040308412 --- /dev/null +++ b/purchase_order_ubl/static/description/index.html @@ -0,0 +1,429 @@ + + + + + + +Purchase Order UBL + + + +
+

Purchase Order UBL

+ + +

Beta License: AGPL-3 OCA/edi Translate me on Weblate Try me on Runbot

+

This module adds support for UBL, the Universal Business Language (UBL) standard, +on purchase orders. The UBL 2.1 standard became the +ISO/IEC 19845 standard +in December 2015 (cf the official announce).

+

With this module, when you generate the purchase order or RFQ report:

+
    +
  • on a draft/RFQ/Bid Received purchase order, the PDF file will have an embedded XML Request For Quotation file compliant with the UBL 2.1 or 2.0 standard.
  • +
  • on an approved purchase order, the PDF file will have an embedded XML Order file compliant with the UBL 2.1 or 2.0 standard.
  • +
+

If your supplier has Odoo and has installed the module sale_order_import_ubl, he will be able to import the PDF file and it will automatically create the quotation/sale order.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/edi project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + From 28f45b7a82565f1c0072ab9787331c91bdf8e6ef Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 2 Apr 2019 10:50:32 +0200 Subject: [PATCH 12/33] [12.0][MIG] purchase_order_ubl --- purchase_order_ubl/__manifest__.py | 4 ++-- purchase_order_ubl/models/purchase.py | 5 +++-- purchase_order_ubl/tests/test_ubl_generate.py | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/purchase_order_ubl/__manifest__.py b/purchase_order_ubl/__manifest__.py index 9854cfb19c..5558738152 100644 --- a/purchase_order_ubl/__manifest__.py +++ b/purchase_order_ubl/__manifest__.py @@ -3,13 +3,13 @@ { 'name': 'Purchase Order UBL', - 'version': '11.0.1.0.0', + 'version': '12.0.1.0.0', 'category': 'Purchase Management', 'license': 'AGPL-3', 'summary': 'Embed UBL XML file inside the PDF purchase order', 'author': 'Akretion,Odoo Community Association (OCA)', 'website': 'https://github.com/OCA/edi/', - 'depends': ['purchase', 'base_ubl'], + 'depends': ['purchase_stock', 'base_ubl'], 'data': [], 'installable': True, } diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index 0a00d233f9..9e3f0c7cf4 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -23,12 +23,13 @@ def get_order_states(self): @api.multi def _ubl_add_header(self, doc_type, parent_node, ns, version='2.1'): if doc_type == 'rfq': - now_utc = fields.Datetime.now() + now_utc = fields.Datetime.to_string(fields.Datetime.now()) date = now_utc[:10] time = now_utc[11:] currency_node_name = 'PricingCurrencyCode' elif doc_type == 'order': - date = self.date_approve or self.date_order[:10] + date = self.date_approve or self.date_order.date() + date = fields.Datetime.to_string(date) currency_node_name = 'DocumentCurrencyCode' ubl_version = etree.SubElement( parent_node, ns['cbc'] + 'UBLVersionID') diff --git a/purchase_order_ubl/tests/test_ubl_generate.py b/purchase_order_ubl/tests/test_ubl_generate.py index 5662ef94b5..ca68da53b5 100644 --- a/purchase_order_ubl/tests/test_ubl_generate.py +++ b/purchase_order_ubl/tests/test_ubl_generate.py @@ -17,7 +17,8 @@ def test_ubl_generate(self): order = self.env.ref('purchase.purchase_order_%d' % i) for version in ['2.0', '2.1']: pdf_file = ro.with_context( - ubl_version=version + ubl_version=version, + force_report_rendering=True ).render_qweb_pdf(order.ids)[0] res = buo.get_xml_files_from_pdf(pdf_file) if order.state in order_states: From 203ebcaaaaaa7162ef7670b664339c9f1e8aea2c Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 2 Apr 2019 14:40:58 +0200 Subject: [PATCH 13/33] [REF] Split purchase_order_ubl and purchase_stock_ubl --- purchase_order_ubl/__manifest__.py | 2 +- purchase_order_ubl/models/purchase.py | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/purchase_order_ubl/__manifest__.py b/purchase_order_ubl/__manifest__.py index 5558738152..7b0e1a447d 100644 --- a/purchase_order_ubl/__manifest__.py +++ b/purchase_order_ubl/__manifest__.py @@ -9,7 +9,7 @@ 'summary': 'Embed UBL XML file inside the PDF purchase order', 'author': 'Akretion,Odoo Community Association (OCA)', 'website': 'https://github.com/OCA/edi/', - 'depends': ['purchase_stock', 'base_ubl'], + 'depends': ['purchase', 'base_ubl'], 'data': [], 'installable': True, } diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index 9e3f0c7cf4..a775e70e7f 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -91,14 +91,8 @@ def _ubl_add_order_line( def get_delivery_partner(self): self.ensure_one() if self.dest_address_id: - partner = self.dest_address_id - elif ( - self.picking_type_id.warehouse_id and - self.picking_type_id.warehouse_id.partner_id): - partner = self.picking_type_id.warehouse_id.partner_id - else: - partner = self.company_id.partner_id - return partner + return self.dest_address_id + return self.company_id.partner_id @api.multi def generate_rfq_ubl_xml_etree(self, version='2.1'): From 1f385ee366f696ff94699453968e95d851734b6e Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 3 Jan 2020 09:27:35 +0100 Subject: [PATCH 14/33] [13.0][MIG] purchase_order_ubl --- purchase_order_ubl/__manifest__.py | 3 +- purchase_order_ubl/models/purchase.py | 51 +++++++++++++-------------- purchase_order_ubl/models/report.py | 43 ++++++++++++++-------- 3 files changed, 55 insertions(+), 42 deletions(-) diff --git a/purchase_order_ubl/__manifest__.py b/purchase_order_ubl/__manifest__.py index 7b0e1a447d..ed790e4ff0 100644 --- a/purchase_order_ubl/__manifest__.py +++ b/purchase_order_ubl/__manifest__.py @@ -3,13 +3,12 @@ { 'name': 'Purchase Order UBL', - 'version': '12.0.1.0.0', + 'version': '13.0.1.0.0', 'category': 'Purchase Management', 'license': 'AGPL-3', 'summary': 'Embed UBL XML file inside the PDF purchase order', 'author': 'Akretion,Odoo Community Association (OCA)', 'website': 'https://github.com/OCA/edi/', 'depends': ['purchase', 'base_ubl'], - 'data': [], 'installable': True, } diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index a775e70e7f..2dc84c535e 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -1,4 +1,5 @@ # © 2016-2017 Akretion (Alexis de Lattre ) +# Copyright 2020 Onestein () # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import models, fields, api @@ -20,7 +21,6 @@ def get_rfq_states(self): def get_order_states(self): return ['purchase', 'done'] - @api.multi def _ubl_add_header(self, doc_type, parent_node, ns, version='2.1'): if doc_type == 'rfq': now_utc = fields.Datetime.to_string(fields.Datetime.now()) @@ -48,7 +48,6 @@ def _ubl_add_header(self, doc_type, parent_node, ns, version='2.1'): parent_node, ns['cbc'] + currency_node_name) doc_currency.text = self.currency_id.name - @api.multi def _ubl_add_monetary_total(self, parent_node, ns, version='2.1'): monetary_total = etree.SubElement( parent_node, ns['cac'] + 'AnticipatedMonetaryTotal') @@ -61,7 +60,6 @@ def _ubl_add_monetary_total(self, parent_node, ns, version='2.1'): currencyID=self.currency_id.name) payable_amount.text = str(self.amount_total) - @api.multi def _ubl_add_rfq_line( self, parent_node, oline, line_number, ns, version='2.1'): line_root = etree.SubElement( @@ -71,7 +69,6 @@ def _ubl_add_rfq_line( oline.product_qty, oline.product_uom, line_root, ns, seller=self.partner_id.commercial_partner_id, version=version) - @api.multi def _ubl_add_order_line( self, parent_node, oline, line_number, ns, version='2.1'): line_root = etree.SubElement( @@ -87,14 +84,12 @@ def _ubl_add_order_line( qty_precision=qty_precision, price_precision=price_precision, version=version) - @api.multi def get_delivery_partner(self): self.ensure_one() if self.dest_address_id: return self.dest_address_id return self.company_id.partner_id - @api.multi def generate_rfq_ubl_xml_etree(self, version='2.1'): nsmap, ns = self._ubl_get_nsmap_namespace( 'RequestForQuotation-2', version=version) @@ -124,7 +119,6 @@ def generate_rfq_ubl_xml_etree(self, version='2.1'): xml_root, oline, line_number, ns, version=version) return xml_root - @api.multi def generate_order_ubl_xml_etree(self, version='2.1'): nsmap, ns = self._ubl_get_nsmap_namespace('Order-2', version=version) xml_root = etree.Element('Order', nsmap=nsmap) @@ -154,7 +148,6 @@ def generate_order_ubl_xml_etree(self, version='2.1'): xml_root, oline, line_number, ns, version=version) return xml_root - @api.multi def generate_ubl_xml_string(self, doc_type, version='2.1'): self.ensure_one() assert doc_type in ('order', 'rfq'), 'wrong doc_type' @@ -184,7 +177,6 @@ def generate_ubl_xml_string(self, doc_type, version='2.1'): logger.debug(xml_string) return xml_string - @api.multi def get_ubl_filename(self, doc_type, version='2.1'): """This method is designed to be inherited""" if doc_type == 'rfq': @@ -192,29 +184,36 @@ def get_ubl_filename(self, doc_type, version='2.1'): elif doc_type == 'order': return 'UBL-Order-%s.xml' % version - @api.multi def get_ubl_version(self): - version = self._context.get('ubl_version') or '2.1' - return version + return self.env.context.get('ubl_version') or '2.1' - @api.multi def get_ubl_lang(self): + self.ensure_one() return self.partner_id.lang or 'en_US' - @api.multi - def embed_ubl_xml_in_pdf(self, pdf_content=None, pdf_file=None): + def add_xml_in_pdf_buffer(self, buffer): self.ensure_one() - doc_type = False - if self.state in self.get_rfq_states(): - doc_type = 'rfq' - elif self.state in self.get_order_states(): - doc_type = 'order' + doc_type = self.get_ubl_purchase_order_doc_type() if doc_type: version = self.get_ubl_version() - ubl_filename = self.get_ubl_filename(doc_type, version=version) - xml_string = self.generate_ubl_xml_string( - doc_type, version=version) - pdf_content = self.embed_xml_in_pdf( - xml_string, ubl_filename, - pdf_content=pdf_content, pdf_file=pdf_file) + xml_filename = self.get_ubl_filename(doc_type, version=version) + xml_string = self.generate_ubl_xml_string(doc_type, version=version) + buffer = self._ubl_add_xml_in_pdf_buffer(xml_string, xml_filename, buffer) + return buffer + + def embed_ubl_xml_in_pdf(self, pdf_content): + self.ensure_one() + doc_type = self.get_ubl_purchase_order_doc_type() + if doc_type: + version = self.get_ubl_version() + xml_filename = self.get_ubl_filename(doc_type, version=version) + xml_string = self.generate_ubl_xml_string(doc_type, version=version) + pdf_content = self.embed_xml_in_pdf(xml_string, xml_filename, pdf_content=pdf_content) return pdf_content + + def get_ubl_purchase_order_doc_type(self): + self.ensure_one() + if self.state in self.get_rfq_states(): + return 'rfq' + elif self.state in self.get_order_states(): + return 'order' diff --git a/purchase_order_ubl/models/report.py b/purchase_order_ubl/models/report.py index 76af6ea015..3d4f69b463 100644 --- a/purchase_order_ubl/models/report.py +++ b/purchase_order_ubl/models/report.py @@ -1,28 +1,43 @@ # © 2016-2017 Akretion (Alexis de Lattre ) +# Copyright 2020 Onestein () # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, models +from odoo import models -class Report(models.Model): +class IrActionsReport(models.Model): _inherit = "ir.actions.report" - @api.multi - def render_qweb_pdf(self, res_ids=None, data=None): + def postprocess_pdf_report(self, record, buffer): + if self.is_ubl_xml_to_embed_in_purchase_order(): + buffer = record.add_xml_in_pdf_buffer(buffer) + return super().postprocess_pdf_report(record, buffer) + + def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None): """We go through that method when the PDF is generated for the 1st time and also when it is read from the attachment. - This method is specific to QWeb""" - pdf_content = super(Report, self).render_qweb_pdf(res_ids, data) - purchase_reports = self._get_purchase_order_ubl_reports() - if ( - len(self) == 1 and - self.report_name in purchase_reports and - len(res_ids) == 1 and - not self._context.get('no_embedded_ubl_xml')): - order = self.env['purchase.order'].browse(res_ids[0]) - pdf_content = order.embed_ubl_xml_in_pdf(pdf_content=pdf_content) + """ + pdf_content = super()._post_pdf( + save_in_attachment, pdf_content=pdf_content, res_ids=res_ids) + if res_ids and len(res_ids) == 1: + if self.is_ubl_xml_to_embed_in_purchase_order(): + purchase_order = self.env['purchase.order'].browse(res_ids) + pdf_content = purchase_order.embed_ubl_xml_in_pdf(pdf_content) return pdf_content + def render_qweb_pdf(self, res_ids=None, data=None): + """This is only necessary when tests are enabled. + It forces the creation of pdf instead of html.""" + if len(res_ids or []) == 1 and not self.env.context.get('no_embedded_ubl_xml'): + if len(self) == 1 and self.is_ubl_xml_to_embed_in_purchase_order(): + self = self.with_context(force_report_rendering=True) + return super().render_qweb_pdf(res_ids, data) + + def is_ubl_xml_to_embed_in_purchase_order(self): + return self.model == 'purchase.order'\ + and not self.env.context.get('no_embedded_ubl_xml')\ + and self.report_name in self._get_purchase_order_ubl_reports() + def _get_purchase_order_ubl_reports(self): return [ 'purchase.report_purchaseorder', From 4960323e1b06c52c31636d61cc639d7cfe8dbc92 Mon Sep 17 00:00:00 2001 From: David Beal Date: Sat, 28 Mar 2020 17:24:04 +0100 Subject: [PATCH 15/33] IMP purch_order: allow to using xml with no pdf and hook to customize xml --- purchase_order_ubl/models/purchase.py | 38 +++++++++++++++++++-------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index 2dc84c535e..3824ea77f2 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -149,6 +149,22 @@ def generate_order_ubl_xml_etree(self, version='2.1'): return xml_root def generate_ubl_xml_string(self, doc_type, version='2.1'): + """ Provide UBL Xml string with no check + According to your use check this string integrity with + _ubl_check_xml_schema() method + """ + self.ensure_one() + xml_root = self.get_ubl_xml_etree(doc_type, version=version) + xml_string = etree.tostring( + xml_root, pretty_print=True, encoding='UTF-8', + xml_declaration=True) + logger.debug( + '%s UBL XML file generated for purchase order ID %d (state %s)', + doc_type, self.id, self.state) + logger.debug(xml_string) + return xml_string + + def get_ubl_xml_etree(self, doc_type, version='2.1'): self.ensure_one() assert doc_type in ('order', 'rfq'), 'wrong doc_type' logger.debug('Starting to generate UBL XML %s file', doc_type) @@ -162,20 +178,18 @@ def generate_ubl_xml_string(self, doc_type, version='2.1'): if doc_type == 'order': xml_root = self.with_context(lang=lang).\ generate_order_ubl_xml_etree(version=version) - document = 'Order' elif doc_type == 'rfq': xml_root = self.with_context(lang=lang).\ generate_rfq_ubl_xml_etree(version=version) - document = 'RequestForQuotation' - xml_string = etree.tostring( - xml_root, pretty_print=True, encoding='UTF-8', - xml_declaration=True) - self._ubl_check_xml_schema(xml_string, document, version=version) - logger.debug( - '%s UBL XML file generated for purchase order ID %d (state %s)', - doc_type, self.id, self.state) - logger.debug(xml_string) - return xml_string + return xml_root + + def get_document_name(self, doc_type): + document = False + if doc_type == "order": + document = "Order" + elif doc_type == "rfq": + document = "RequestForQuotation" + return document def get_ubl_filename(self, doc_type, version='2.1'): """This method is designed to be inherited""" @@ -208,6 +222,8 @@ def embed_ubl_xml_in_pdf(self, pdf_content): version = self.get_ubl_version() xml_filename = self.get_ubl_filename(doc_type, version=version) xml_string = self.generate_ubl_xml_string(doc_type, version=version) + self._ubl_check_xml_schema( + xml_string, self.get_document_name(doc_type), version=version) pdf_content = self.embed_xml_in_pdf(xml_string, xml_filename, pdf_content=pdf_content) return pdf_content From d5d7841cfd3d13d86251d41f3de4cf6539f9b1ec Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 30 Mar 2020 10:01:39 +0200 Subject: [PATCH 16/33] pre-commit --- purchase_order_ubl/__manifest__.py | 18 +- purchase_order_ubl/models/purchase.py | 223 ++++++++++-------- purchase_order_ubl/models/report.py | 19 +- purchase_order_ubl/tests/test_ubl_generate.py | 19 +- 4 files changed, 155 insertions(+), 124 deletions(-) diff --git a/purchase_order_ubl/__manifest__.py b/purchase_order_ubl/__manifest__.py index ed790e4ff0..f09fae796c 100644 --- a/purchase_order_ubl/__manifest__.py +++ b/purchase_order_ubl/__manifest__.py @@ -2,13 +2,13 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { - 'name': 'Purchase Order UBL', - 'version': '13.0.1.0.0', - 'category': 'Purchase Management', - 'license': 'AGPL-3', - 'summary': 'Embed UBL XML file inside the PDF purchase order', - 'author': 'Akretion,Odoo Community Association (OCA)', - 'website': 'https://github.com/OCA/edi/', - 'depends': ['purchase', 'base_ubl'], - 'installable': True, + "name": "Purchase Order UBL", + "version": "13.0.1.0.0", + "category": "Purchase Management", + "license": "AGPL-3", + "summary": "Embed UBL XML file inside the PDF purchase order", + "author": "Akretion,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/edi/", + "depends": ["purchase", "base_ubl"], + "installable": True, } diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index 3824ea77f2..98e6f35c87 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -2,87 +2,105 @@ # Copyright 2020 Onestein () # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import models, fields, api -from lxml import etree import logging +from lxml import etree + +from odoo import api, fields, models + logger = logging.getLogger(__name__) class PurchaseOrder(models.Model): - _name = 'purchase.order' - _inherit = ['purchase.order', 'base.ubl'] + _name = "purchase.order" + _inherit = ["purchase.order", "base.ubl"] @api.model def get_rfq_states(self): - return ['draft', 'sent', 'to approve'] + return ["draft", "sent", "to approve"] @api.model def get_order_states(self): - return ['purchase', 'done'] + return ["purchase", "done"] - def _ubl_add_header(self, doc_type, parent_node, ns, version='2.1'): - if doc_type == 'rfq': + def _ubl_add_header(self, doc_type, parent_node, ns, version="2.1"): + if doc_type == "rfq": now_utc = fields.Datetime.to_string(fields.Datetime.now()) date = now_utc[:10] time = now_utc[11:] - currency_node_name = 'PricingCurrencyCode' - elif doc_type == 'order': + currency_node_name = "PricingCurrencyCode" + elif doc_type == "order": date = self.date_approve or self.date_order.date() date = fields.Datetime.to_string(date) - currency_node_name = 'DocumentCurrencyCode' - ubl_version = etree.SubElement( - parent_node, ns['cbc'] + 'UBLVersionID') + currency_node_name = "DocumentCurrencyCode" + ubl_version = etree.SubElement(parent_node, ns["cbc"] + "UBLVersionID") ubl_version.text = version - doc_id = etree.SubElement(parent_node, ns['cbc'] + 'ID') + doc_id = etree.SubElement(parent_node, ns["cbc"] + "ID") doc_id.text = self.name - issue_date = etree.SubElement(parent_node, ns['cbc'] + 'IssueDate') + issue_date = etree.SubElement(parent_node, ns["cbc"] + "IssueDate") issue_date.text = date - if doc_type == 'rfq': # IssueTime is required on RFQ, not on order - issue_time = etree.SubElement(parent_node, ns['cbc'] + 'IssueTime') + if doc_type == "rfq": # IssueTime is required on RFQ, not on order + issue_time = etree.SubElement(parent_node, ns["cbc"] + "IssueTime") issue_time.text = time if self.notes: - note = etree.SubElement(parent_node, ns['cbc'] + 'Note') + note = etree.SubElement(parent_node, ns["cbc"] + "Note") note.text = self.notes - doc_currency = etree.SubElement( - parent_node, ns['cbc'] + currency_node_name) + doc_currency = etree.SubElement(parent_node, ns["cbc"] + currency_node_name) doc_currency.text = self.currency_id.name - def _ubl_add_monetary_total(self, parent_node, ns, version='2.1'): + def _ubl_add_monetary_total(self, parent_node, ns, version="2.1"): monetary_total = etree.SubElement( - parent_node, ns['cac'] + 'AnticipatedMonetaryTotal') + parent_node, ns["cac"] + "AnticipatedMonetaryTotal" + ) line_total = etree.SubElement( - monetary_total, ns['cbc'] + 'LineExtensionAmount', - currencyID=self.currency_id.name) + monetary_total, + ns["cbc"] + "LineExtensionAmount", + currencyID=self.currency_id.name, + ) line_total.text = str(self.amount_untaxed) payable_amount = etree.SubElement( - monetary_total, ns['cbc'] + 'PayableAmount', - currencyID=self.currency_id.name) + monetary_total, + ns["cbc"] + "PayableAmount", + currencyID=self.currency_id.name, + ) payable_amount.text = str(self.amount_total) - def _ubl_add_rfq_line( - self, parent_node, oline, line_number, ns, version='2.1'): - line_root = etree.SubElement( - parent_node, ns['cac'] + 'RequestForQuotationLine') + def _ubl_add_rfq_line(self, parent_node, oline, line_number, ns, version="2.1"): + line_root = etree.SubElement(parent_node, ns["cac"] + "RequestForQuotationLine") self._ubl_add_line_item( - line_number, oline.name, oline.product_id, 'purchase', - oline.product_qty, oline.product_uom, line_root, ns, - seller=self.partner_id.commercial_partner_id, version=version) - - def _ubl_add_order_line( - self, parent_node, oline, line_number, ns, version='2.1'): - line_root = etree.SubElement( - parent_node, ns['cac'] + 'OrderLine') - dpo = self.env['decimal.precision'] - qty_precision = dpo.precision_get('Product Unit of Measure') - price_precision = dpo.precision_get('Product Price') + line_number, + oline.name, + oline.product_id, + "purchase", + oline.product_qty, + oline.product_uom, + line_root, + ns, + seller=self.partner_id.commercial_partner_id, + version=version, + ) + + def _ubl_add_order_line(self, parent_node, oline, line_number, ns, version="2.1"): + line_root = etree.SubElement(parent_node, ns["cac"] + "OrderLine") + dpo = self.env["decimal.precision"] + qty_precision = dpo.precision_get("Product Unit of Measure") + price_precision = dpo.precision_get("Product Price") self._ubl_add_line_item( - line_number, oline.name, oline.product_id, 'purchase', - oline.product_qty, oline.product_uom, line_root, ns, + line_number, + oline.name, + oline.product_id, + "purchase", + oline.product_qty, + oline.product_uom, + line_root, + ns, seller=self.partner_id.commercial_partner_id, - currency=self.currency_id, price_subtotal=oline.price_subtotal, - qty_precision=qty_precision, price_precision=price_precision, - version=version) + currency=self.currency_id, + price_subtotal=oline.price_subtotal, + qty_precision=qty_precision, + price_precision=price_precision, + version=version, + ) def get_delivery_partner(self): self.ensure_one() @@ -90,65 +108,72 @@ def get_delivery_partner(self): return self.dest_address_id return self.company_id.partner_id - def generate_rfq_ubl_xml_etree(self, version='2.1'): + def generate_rfq_ubl_xml_etree(self, version="2.1"): nsmap, ns = self._ubl_get_nsmap_namespace( - 'RequestForQuotation-2', version=version) - xml_root = etree.Element('RequestForQuotation', nsmap=nsmap) - doc_type = 'rfq' + "RequestForQuotation-2", version=version + ) + xml_root = etree.Element("RequestForQuotation", nsmap=nsmap) + doc_type = "rfq" self._ubl_add_header(doc_type, xml_root, ns, version=version) # The order of SellerSupplierParty / BuyerCustomerParty is different # between RFQ and Order ! self._ubl_add_supplier_party( - self.partner_id, False, 'SellerSupplierParty', xml_root, ns, - version=version) - if version == '2.1': + self.partner_id, False, "SellerSupplierParty", xml_root, ns, version=version + ) + if version == "2.1": self._ubl_add_customer_party( - False, self.company_id, 'BuyerCustomerParty', xml_root, ns, - version=version) + False, + self.company_id, + "BuyerCustomerParty", + xml_root, + ns, + version=version, + ) delivery_partner = self.get_delivery_partner() self._ubl_add_delivery(delivery_partner, xml_root, ns, version=version) if self.incoterm_id: self._ubl_add_delivery_terms( - self.incoterm_id, xml_root, ns, version=version) + self.incoterm_id, xml_root, ns, version=version + ) line_number = 0 for oline in self.order_line: line_number += 1 - self._ubl_add_rfq_line( - xml_root, oline, line_number, ns, version=version) + self._ubl_add_rfq_line(xml_root, oline, line_number, ns, version=version) return xml_root - def generate_order_ubl_xml_etree(self, version='2.1'): - nsmap, ns = self._ubl_get_nsmap_namespace('Order-2', version=version) - xml_root = etree.Element('Order', nsmap=nsmap) - doc_type = 'order' + def generate_order_ubl_xml_etree(self, version="2.1"): + nsmap, ns = self._ubl_get_nsmap_namespace("Order-2", version=version) + xml_root = etree.Element("Order", nsmap=nsmap) + doc_type = "order" self._ubl_add_header(doc_type, xml_root, ns, version=version) self._ubl_add_customer_party( - False, self.company_id, 'BuyerCustomerParty', xml_root, ns, - version=version) + False, self.company_id, "BuyerCustomerParty", xml_root, ns, version=version + ) self._ubl_add_supplier_party( - self.partner_id, False, 'SellerSupplierParty', xml_root, ns, - version=version) + self.partner_id, False, "SellerSupplierParty", xml_root, ns, version=version + ) delivery_partner = self.get_delivery_partner() self._ubl_add_delivery(delivery_partner, xml_root, ns, version=version) if self.incoterm_id: self._ubl_add_delivery_terms( - self.incoterm_id, xml_root, ns, version=version) + self.incoterm_id, xml_root, ns, version=version + ) if self.payment_term_id: self._ubl_add_payment_terms( - self.payment_term_id, xml_root, ns, version=version) + self.payment_term_id, xml_root, ns, version=version + ) self._ubl_add_monetary_total(xml_root, ns, version=version) line_number = 0 for oline in self.order_line: line_number += 1 - self._ubl_add_order_line( - xml_root, oline, line_number, ns, version=version) + self._ubl_add_order_line(xml_root, oline, line_number, ns, version=version) return xml_root - def generate_ubl_xml_string(self, doc_type, version='2.1'): + def generate_ubl_xml_string(self, doc_type, version="2.1"): """ Provide UBL Xml string with no check According to your use check this string integrity with _ubl_check_xml_schema() method @@ -156,18 +181,21 @@ def generate_ubl_xml_string(self, doc_type, version='2.1'): self.ensure_one() xml_root = self.get_ubl_xml_etree(doc_type, version=version) xml_string = etree.tostring( - xml_root, pretty_print=True, encoding='UTF-8', - xml_declaration=True) + xml_root, pretty_print=True, encoding="UTF-8", xml_declaration=True + ) logger.debug( - '%s UBL XML file generated for purchase order ID %d (state %s)', - doc_type, self.id, self.state) + "%s UBL XML file generated for purchase order ID %d (state %s)", + doc_type, + self.id, + self.state, + ) logger.debug(xml_string) return xml_string - def get_ubl_xml_etree(self, doc_type, version='2.1'): + def get_ubl_xml_etree(self, doc_type, version="2.1"): self.ensure_one() - assert doc_type in ('order', 'rfq'), 'wrong doc_type' - logger.debug('Starting to generate UBL XML %s file', doc_type) + assert doc_type in ("order", "rfq"), "wrong doc_type" + logger.debug("Starting to generate UBL XML %s file", doc_type) lang = self.get_ubl_lang() # The aim of injecting lang in context # is to have the content of the XML in the partner's lang @@ -175,12 +203,14 @@ def get_ubl_xml_etree(self, doc_type, version='2.1'): # that lang. But the error messages should almost never # happen except the first days of use, so it's probably # not worth the additional code to handle the 2 langs - if doc_type == 'order': - xml_root = self.with_context(lang=lang).\ - generate_order_ubl_xml_etree(version=version) - elif doc_type == 'rfq': - xml_root = self.with_context(lang=lang).\ - generate_rfq_ubl_xml_etree(version=version) + if doc_type == "order": + xml_root = self.with_context(lang=lang).generate_order_ubl_xml_etree( + version=version + ) + elif doc_type == "rfq": + xml_root = self.with_context(lang=lang).generate_rfq_ubl_xml_etree( + version=version + ) return xml_root def get_document_name(self, doc_type): @@ -191,19 +221,19 @@ def get_document_name(self, doc_type): document = "RequestForQuotation" return document - def get_ubl_filename(self, doc_type, version='2.1'): + def get_ubl_filename(self, doc_type, version="2.1"): """This method is designed to be inherited""" - if doc_type == 'rfq': - return 'UBL-RequestForQuotation-%s.xml' % version - elif doc_type == 'order': - return 'UBL-Order-%s.xml' % version + if doc_type == "rfq": + return "UBL-RequestForQuotation-%s.xml" % version + elif doc_type == "order": + return "UBL-Order-%s.xml" % version def get_ubl_version(self): - return self.env.context.get('ubl_version') or '2.1' + return self.env.context.get("ubl_version") or "2.1" def get_ubl_lang(self): self.ensure_one() - return self.partner_id.lang or 'en_US' + return self.partner_id.lang or "en_US" def add_xml_in_pdf_buffer(self, buffer): self.ensure_one() @@ -223,13 +253,16 @@ def embed_ubl_xml_in_pdf(self, pdf_content): xml_filename = self.get_ubl_filename(doc_type, version=version) xml_string = self.generate_ubl_xml_string(doc_type, version=version) self._ubl_check_xml_schema( - xml_string, self.get_document_name(doc_type), version=version) - pdf_content = self.embed_xml_in_pdf(xml_string, xml_filename, pdf_content=pdf_content) + xml_string, self.get_document_name(doc_type), version=version + ) + pdf_content = self.embed_xml_in_pdf( + xml_string, xml_filename, pdf_content=pdf_content + ) return pdf_content def get_ubl_purchase_order_doc_type(self): self.ensure_one() if self.state in self.get_rfq_states(): - return 'rfq' + return "rfq" elif self.state in self.get_order_states(): - return 'order' + return "order" diff --git a/purchase_order_ubl/models/report.py b/purchase_order_ubl/models/report.py index 3d4f69b463..e7ad226294 100644 --- a/purchase_order_ubl/models/report.py +++ b/purchase_order_ubl/models/report.py @@ -18,27 +18,28 @@ def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None): time and also when it is read from the attachment. """ pdf_content = super()._post_pdf( - save_in_attachment, pdf_content=pdf_content, res_ids=res_ids) + save_in_attachment, pdf_content=pdf_content, res_ids=res_ids + ) if res_ids and len(res_ids) == 1: if self.is_ubl_xml_to_embed_in_purchase_order(): - purchase_order = self.env['purchase.order'].browse(res_ids) + purchase_order = self.env["purchase.order"].browse(res_ids) pdf_content = purchase_order.embed_ubl_xml_in_pdf(pdf_content) return pdf_content def render_qweb_pdf(self, res_ids=None, data=None): """This is only necessary when tests are enabled. It forces the creation of pdf instead of html.""" - if len(res_ids or []) == 1 and not self.env.context.get('no_embedded_ubl_xml'): + if len(res_ids or []) == 1 and not self.env.context.get("no_embedded_ubl_xml"): if len(self) == 1 and self.is_ubl_xml_to_embed_in_purchase_order(): self = self.with_context(force_report_rendering=True) return super().render_qweb_pdf(res_ids, data) def is_ubl_xml_to_embed_in_purchase_order(self): - return self.model == 'purchase.order'\ - and not self.env.context.get('no_embedded_ubl_xml')\ - and self.report_name in self._get_purchase_order_ubl_reports() + return ( + self.model == "purchase.order" + and not self.env.context.get("no_embedded_ubl_xml") + and self.report_name in self._get_purchase_order_ubl_reports() + ) def _get_purchase_order_ubl_reports(self): - return [ - 'purchase.report_purchaseorder', - 'purchase.report_purchasequotation'] + return ["purchase.report_purchaseorder", "purchase.report_purchasequotation"] diff --git a/purchase_order_ubl/tests/test_ubl_generate.py b/purchase_order_ubl/tests/test_ubl_generate.py index ca68da53b5..221c697943 100644 --- a/purchase_order_ubl/tests/test_ubl_generate.py +++ b/purchase_order_ubl/tests/test_ubl_generate.py @@ -5,26 +5,23 @@ class TestUblOrder(HttpCase): - def test_ubl_generate(self): - ro = self.env.ref('purchase.report_purchase_quotation') - poo = self.env['purchase.order'] - buo = self.env['base.ubl'] + ro = self.env.ref("purchase.report_purchase_quotation") + poo = self.env["purchase.order"] + buo = self.env["base.ubl"] order_states = poo.get_order_states() rfq_states = poo.get_rfq_states() for i in range(7): i += 1 - order = self.env.ref('purchase.purchase_order_%d' % i) - for version in ['2.0', '2.1']: + order = self.env.ref("purchase.purchase_order_%d" % i) + for version in ["2.0", "2.1"]: pdf_file = ro.with_context( - ubl_version=version, - force_report_rendering=True + ubl_version=version, force_report_rendering=True ).render_qweb_pdf(order.ids)[0] res = buo.get_xml_files_from_pdf(pdf_file) if order.state in order_states: - filename = order.get_ubl_filename( - 'order', version=version) + filename = order.get_ubl_filename("order", version=version) self.assertTrue(filename in res) elif order.state in rfq_states: - filename = order.get_ubl_filename('rfq', version=version) + filename = order.get_ubl_filename("rfq", version=version) self.assertTrue(filename in res) From 634ce3218df752418e6cea12a62d2f092cc9bdc3 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 4 May 2020 11:23:04 +0200 Subject: [PATCH 17/33] Code review [UPD] Update purchase_order_ubl.pot [UPD] README.rst [ADD] icon.png --- purchase_order_ubl/README.rst | 10 +++++----- purchase_order_ubl/i18n/purchase_order_ubl.pot | 9 ++++----- purchase_order_ubl/models/purchase.py | 10 ++++++---- purchase_order_ubl/static/description/icon.png | Bin 0 -> 9455 bytes purchase_order_ubl/static/description/index.html | 8 ++++---- 5 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 purchase_order_ubl/static/description/icon.png diff --git a/purchase_order_ubl/README.rst b/purchase_order_ubl/README.rst index 3d3f1f1a1c..b00ac6b969 100644 --- a/purchase_order_ubl/README.rst +++ b/purchase_order_ubl/README.rst @@ -14,13 +14,13 @@ Purchase Order UBL :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github - :target: https://github.com/OCA/edi/tree/11.0/purchase_order_ubl + :target: https://github.com/OCA/edi/tree/13.0/purchase_order_ubl :alt: OCA/edi .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/edi-11-0/edi-11-0-purchase_order_ubl + :target: https://translation.odoo-community.org/projects/edi-13-0/edi-13-0-purchase_order_ubl :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/226/11.0 + :target: https://runbot.odoo-community.org/runbot/226/13.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -49,7 +49,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -80,6 +80,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/edi `_ project on GitHub. +This module is part of the `OCA/edi `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/purchase_order_ubl/i18n/purchase_order_ubl.pot b/purchase_order_ubl/i18n/purchase_order_ubl.pot index 471e5815ad..637e206220 100644 --- a/purchase_order_ubl/i18n/purchase_order_ubl.pot +++ b/purchase_order_ubl/i18n/purchase_order_ubl.pot @@ -1,12 +1,12 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * purchase_order_ubl +# * purchase_order_ubl # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 11.0\n" +"Project-Id-Version: Odoo Server 13.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: <>\n" +"Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,6 +20,5 @@ msgstr "" #. module: purchase_order_ubl #: model:ir.model,name:purchase_order_ubl.model_ir_actions_report -msgid "ir.actions.report" +msgid "Report Action" msgstr "" - diff --git a/purchase_order_ubl/models/purchase.py b/purchase_order_ubl/models/purchase.py index 98e6f35c87..233d6d7a6f 100644 --- a/purchase_order_ubl/models/purchase.py +++ b/purchase_order_ubl/models/purchase.py @@ -30,8 +30,8 @@ def _ubl_add_header(self, doc_type, parent_node, ns, version="2.1"): time = now_utc[11:] currency_node_name = "PricingCurrencyCode" elif doc_type == "order": - date = self.date_approve or self.date_order.date() - date = fields.Datetime.to_string(date) + date = self.date_approve or self.date_order + date = fields.Date.to_string(date) currency_node_name = "DocumentCurrencyCode" ubl_version = etree.SubElement(parent_node, ns["cbc"] + "UBLVersionID") ubl_version.text = version @@ -262,7 +262,9 @@ def embed_ubl_xml_in_pdf(self, pdf_content): def get_ubl_purchase_order_doc_type(self): self.ensure_one() + doc_type = False if self.state in self.get_rfq_states(): - return "rfq" + doc_type = "rfq" elif self.state in self.get_order_states(): - return "order" + doc_type = "order" + return doc_type diff --git a/purchase_order_ubl/static/description/icon.png b/purchase_order_ubl/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/purchase_order_ubl/static/description/index.html b/purchase_order_ubl/static/description/index.html index d040308412..1a443dace3 100644 --- a/purchase_order_ubl/static/description/index.html +++ b/purchase_order_ubl/static/description/index.html @@ -3,7 +3,7 @@ - + Purchase Order UBL