diff --git a/datev_export_xml/__manifest__.py b/datev_export_xml/__manifest__.py index 7573a2ee..6e006ee1 100644 --- a/datev_export_xml/__manifest__.py +++ b/datev_export_xml/__manifest__.py @@ -10,7 +10,7 @@ { "name": "Datev Export XML", - "version": "16.0.1.0.1", + "version": "16.0.1.1.1", "category": "Accounting", "license": "AGPL-3", "author": "Guenter Selbert, Thorsten Vocks, Maciej Wichowski, Daniela Scarpa, " diff --git a/datev_export_xml/controllers/main.py b/datev_export_xml/controllers/main.py index a8151e1e..d6b4b7cb 100644 --- a/datev_export_xml/controllers/main.py +++ b/datev_export_xml/controllers/main.py @@ -7,15 +7,15 @@ from odoo import http from odoo.http import request, send_file -from odoo.addons.web.controllers.main import Home +from odoo.addons.web.controllers.home import Home _logger = logging.getLogger(__name__) class DatevHome(Home): - @http.route("/datev/xml/download/", type="http", auth="user") - def datev_xml_download_attachment(self, export_id): - export = request.env["datev.export.xml"].search([("id", "=", export_id)]) + @http.route("/datev/xml/download/", type="http", auth="user") + def datev_xml_download_attachment(self, line_id): + export = request.env["datev.export.xml.line"].search([("id", "=", line_id)]) if not export.attachment_id: return request.not_found() diff --git a/datev_export_xml/demo/export_data.xml b/datev_export_xml/demo/export_data.xml index ba212f74..b55d5931 100644 --- a/datev_export_xml/demo/export_data.xml +++ b/datev_export_xml/demo/export_data.xml @@ -260,6 +260,9 @@ A-Customer Child contact + Straße 1 + 12345 + Berlin BE0477472701 diff --git a/datev_export_xml/i18n/de.po b/datev_export_xml/i18n/de.po index 13d0f1af..df703c7f 100644 --- a/datev_export_xml/i18n/de.po +++ b/datev_export_xml/i18n/de.po @@ -398,9 +398,12 @@ msgstr "Dateigröße" #, python-format msgid "" "Filtered Export of %(count)s Documents\n" -" Date Range: %(start)s-%(end)s\n" +"Date Range: %(start)s-%(stop)s\n" "Types: %(types)s" msgstr "" +"Gefilterter Export von %(count)s Dokumente\n" +"Datumsbereich: %(start)s-%(stop)s\n" +"Typen: %(types)s" #. module: datev_export_xml #: model:ir.model.fields,field_description:datev_export_xml.field_datev_export_xml__message_follower_ids @@ -551,9 +554,11 @@ msgstr "" #: code:addons/datev_export_xml/models/datev_export.py:0 #, python-format msgid "" -"Manually Doc Export of %(count)s Documents \n" +"Manual Export of %(count)s Documents\n" "Numbers: %(names)s" msgstr "" +"Manueller Export von %(count)s Documenten \n" +"Nummern: %(names)s" #. module: datev_export_xml #: model:ir.model.fields,field_description:datev_export_xml.field_datev_export_xml__manually_document_selection diff --git a/datev_export_xml/migrations/16.0.1.1.1/post-migrate.py b/datev_export_xml/migrations/16.0.1.1.1/post-migrate.py new file mode 100644 index 00000000..0956f79c --- /dev/null +++ b/datev_export_xml/migrations/16.0.1.1.1/post-migrate.py @@ -0,0 +1,34 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + env = api.Environment(cr, SUPERUSER_ID, {}) + + query = """ + SELECT id, attachment_id FROM datev_export_xml + WHERE attachment_id IS NOT NULL + """ + env.cr.execute(query) + + for export_id, attachment_id in env.cr.fetchall(): + export = env["datev.export.xml"].browse(export_id) + attachment = env["ir.attachment"].browse(attachment_id) + + _logger.info(f"Migrating attachment of {export}") + + line = export.line_ids.create( + { + "attachment_id": attachment_id, + "export_id": export_id, + "invoice_ids": [(6, 0, export.invoice_ids.ids)], + } + ) + + attachment.write({"res_model": line._name, "res_id": line.id}) diff --git a/datev_export_xml/models/__init__.py b/datev_export_xml/models/__init__.py index 9e0261c1..f1f109b0 100644 --- a/datev_export_xml/models/__init__.py +++ b/datev_export_xml/models/__init__.py @@ -18,4 +18,4 @@ res_config_settings, ) -from . import datev_export # isort:skip +from . import datev_export, datev_export_line # isort:skip diff --git a/datev_export_xml/models/datev_export.py b/datev_export_xml/models/datev_export.py index 4e841ba6..5f05e824 100644 --- a/datev_export_xml/models/datev_export.py +++ b/datev_export_xml/models/datev_export.py @@ -14,7 +14,6 @@ from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError -from odoo.tools import human_size _logger = logging.getLogger(__name__) @@ -111,22 +110,23 @@ def name_get(self): readonly=True, states={"draft": [("readonly", False)]}, ) - attachment_id = fields.Many2one( - comodel_name="ir.attachment", string="Attachment", required=False, readonly=True - ) - datev_filename = fields.Char( - "ZIP filename", readonly=True, related="attachment_id.name" - ) - datev_filesize = fields.Char( - "Filesize", - compute="_compute_datev_filesize", + line_ids = fields.One2many( + "datev.export.xml.line", + "export_id", + "Lines", ) + line_count = fields.Integer(compute="_compute_line_count") problematic_invoices_count = fields.Integer( compute="_compute_problematic_invoices_count" ) invoice_ids = fields.Many2many(comodel_name="account.move", string="Invoices") - invoices_count = fields.Integer(compute="_compute_invoices_count", store=True) + invoices_count = fields.Integer( + string="Total Invoices", compute="_compute_invoices_count", store=True + ) + invoices_exported_count = fields.Integer( + string="Exported Invoices", compute="_compute_invoices_count", store=True + ) manually_document_selection = fields.Boolean(default=False) exception_info = fields.Text(readonly=True) @@ -146,22 +146,23 @@ def name_get(self): tracking=True, ) - @api.depends("attachment_id", "attachment_id.file_size") - def _compute_datev_filesize(self): - for r in self: - r.datev_filesize = human_size(r.attachment_id.file_size) + @api.depends("line_ids") + def _compute_line_count(self): + for rec in self: + rec.line_count = len(rec.line_ids) @api.depends("invoice_ids") def _compute_problematic_invoices_count(self): - for r in self: - r.problematic_invoices_count = len( - r.invoice_ids.filtered("datev_validation") + for rec in self: + rec.problematic_invoices_count = len( + rec.invoice_ids.filtered("datev_validation") ) - @api.depends("invoice_ids") + @api.depends("invoice_ids", "line_ids", "line_ids.invoice_ids") def _compute_invoices_count(self): - for r in self: - r.invoices_count = len(r.invoice_ids) + for rec in self: + rec.invoices_count = len(rec.invoice_ids) + rec.invoices_exported_count = len(rec.mapped("line_ids.invoice_ids")) @api.constrains("export_invoice", "export_refund", "export_type") def validate_types(self): @@ -179,14 +180,6 @@ def validate_types(self): ) ) - def datev_download(self): - self.ensure_one() - return { - "type": "ir.actions.act_url", - "url": f"/datev/xml/download/{self.id}", - "target": "self", - } - def get_type_list(self): list_invoice_type = [] if self.export_type: @@ -222,49 +215,67 @@ def get_invoices(self): ) return self.env["account.move"].search(search_clause) - def get_zip(self): - self = self.with_context(bin_size=False) - try: - if self.attachment_id: - self.attachment_id.unlink() + def _get_zip(self): + generator = self.generate_zip( + self.invoice_ids - self.mapped("line_ids.invoice_ids"), + self.check_xsd, + ) - self.write({"state": "running", "exception_info": None}) - with self.env.cr.savepoint(): - zip_file = self.generate_zip( - self.invoice_ids, - self.check_xsd, + for index, (zip_file, invoices) in enumerate(generator, 1): + if not self.manually_document_selection: + description = _( + "Filtered Export of %(count)s Documents\n" + "Date Range: %(start)s-%(stop)s\nTypes: %(types)s", + count=len(invoices), + start=self.date_start, + stop=self.date_stop, + types=", ".join(self.get_type_list()), ) - if not self.manually_document_selection: - description = _( - "Filtered Export of %(count)s Documents\n " - "Date Range: %(start)s-%(end)s\nTypes: %(types)s", - count=len(self.invoice_ids), - start=self.date_start, - end=self.date_stop, - types=", ".join(self.get_type_list()), - ) - else: - description = _( - "Manually Doc Export of %(count)s Documents \nNumbers: %(names)s", - count=len(self.invoice_ids), - names=", ".join(self.invoice_ids.mapped("name")), - ) - - attachment = self.env["ir.attachment"].create( - { - "name": time.strftime("%Y_%m_%d_%H_%M") + ".zip", - "datas": zip_file, - "res_model": "datev.export.xml", - "res_id": self.id, - "res_field": "attachment_id", - "description": description, - } + else: + description = _( + "Manual Export of %(count)s Documents\n" "Numbers: %(names)s", + count=len(invoices), + names=", ".join(self.invoice_ids.mapped("name")), ) - self.write({"attachment_id": attachment.id, "state": "done"}) + + attachment = self.env["ir.attachment"].create( + { + "name": time.strftime(f"%Y-%m-%d_%H%M-{index}.zip"), + "datas": zip_file, + "res_model": "datev.export.xml", + "res_id": self.id, + "res_field": "attachment_id", + "description": description, + } + ) + self.line_ids.sudo().create( + { + "export_id": self.id, + "attachment_id": attachment.id, + "invoice_ids": [(6, 0, invoices.ids)], + } + ) + + # Huge numbers of invoices can lead to cron timeouts. Commit after + # each package and continue. When the timeout hits the job is still + # in running and is set to pending in the cron (hanging job) and + # will continue with the next package + if self.env.context.get("datev_autocommit"): + # pylint: disable=invalid-commit + self.env.cr.commit() + + def get_zip(self): + self.ensure_one() + + try: + self.write({"state": "running", "exception_info": None}) + self.with_context(bin_size=False)._get_zip() + if self.invoices_count == self.invoices_exported_count: + self.write({"state": "done"}) except Exception as e: + _logger.exception(e) msg = e.name if hasattr(e, "name") else str(e) self.write({"exception_info": msg, "state": "failed"}) - _logger.exception(e) self._compute_problematic_invoices_count() @@ -279,14 +290,25 @@ def cron_run_pending_export(self): ) hanging_datev_exports.write({"state": "pending"}) datev_export = self.search( - [("state", "=", "pending"), ("manually_document_selection", "=", False)], + [ + ("state", "in", ("running", "pending")), + ("manually_document_selection", "=", False), + ], + # Favor hanging jobs + order="state DESC", limit=1, ) - if datev_export: - datev_export.with_user(datev_export.create_uid.id).get_zip() + + if not datev_export: + return + + datev_export.with_user(datev_export.create_uid.id).with_context( + datev_autocommit=True + ).get_zip() + + if datev_export.state == "done": datev_export._create_activity() datev_export.invoice_ids.write({"datev_exported": True}) - return True def export_zip(self): self.ensure_one() @@ -297,7 +319,6 @@ def export_zip(self): else: self.invoice_ids = [(6, 0, self.get_invoices().ids)] self.action_pending() - return True @api.model def export_zip_invoice(self, invoice_ids=None): @@ -363,6 +384,9 @@ def _create_activity(self): } ) + def action_invalidate_lines(self): + self.line_ids.write({"invalidated": True}) + def action_validate(self): generator = self.env["datev.xml.generator"] for invoice in self.invoice_ids: @@ -412,12 +436,12 @@ def action_pending(self): ) def action_draft(self): - for r in self: - if r.state == "running": - raise ValidationError( - _("It's not allowed to set a running export to draft!") - ) - r.write({"state": "draft"}) + if "running" in self.mapped("state"): + raise ValidationError( + _("It's not allowed to set a running export to draft!") + ) + + self.write({"state": "draft", "line_ids": [(5,)]}) def action_show_invalid_invoices_view(self): tree_view = self.env.ref("datev_export_xml.view_move_datev_validation") @@ -462,6 +486,7 @@ def write(self, vals): for r in self: if r.manually_document_selection: continue + super(DatevExport, r).write( {"invoice_ids": [(6, 0, r.get_invoices().ids)]} ) diff --git a/datev_export_xml/models/datev_export_line.py b/datev_export_xml/models/datev_export_line.py new file mode 100644 index 00000000..132a9339 --- /dev/null +++ b/datev_export_xml/models/datev_export_line.py @@ -0,0 +1,53 @@ +# © 2024 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.tools import human_size + + +class DatevExportPackage(models.Model): + _name = "datev.export.xml.line" + _description = "DATEV XML Export Line" + + export_id = fields.Many2one("datev.export.xml", readonly=True) + attachment_id = fields.Many2one("ir.attachment", ondelete="cascade", readonly=True) + filename = fields.Char(readonly=True, related="attachment_id.name") + filesize = fields.Char(compute="_compute_filesize") + invoice_ids = fields.Many2many("account.move", readonly=True) + invoices_count = fields.Integer( + string="Invoices", compute="_compute_invoices_count", store=True + ) + + @api.depends("invoice_ids") + def _compute_invoices_count(self): + for line in self: + line.invoices_count = len(line.invoice_ids) + + @api.depends("attachment_id", "attachment_id.file_size") + def _compute_filesize(self): + for line in self: + line.filesize = human_size(line.attachment_id.file_size) + + def action_datev_download(self): + self.ensure_one() + return { + "type": "ir.actions.act_url", + "url": f"/datev/xml/download/{self.id}", + "target": "self", + } + + def action_open_invoices(self): + return { + "type": "ir.actions.act_window", + "view_mode": "tree,kanban,form", + "res_model": "account.move", + "target": "current", + "name": _("Included Invoices"), + "domain": [("id", "in", self.invoice_ids.ids)], + } + + def unlink(self): + attachments = self.mapped("attachment_id") + if attachments: + attachments.unlink() + return super().unlink() diff --git a/datev_export_xml/models/datev_zip_generator.py b/datev_export_xml/models/datev_zip_generator.py index af476afb..b003c1e7 100644 --- a/datev_export_xml/models/datev_zip_generator.py +++ b/datev_export_xml/models/datev_zip_generator.py @@ -41,31 +41,47 @@ def check_valid_data(self, invoices): @api.model def generate_zip(self, invoices, check_xsd): + if not invoices: + return + self.check_valid_data(invoices) - with io.BytesIO() as s, zipfile.ZipFile(s, mode="w") as zip_file: - xml_document_data = self.generate_xml_document(invoices, check_xsd) - zip_file.writestr( - xml_document_data[0], - xml_document_data[1], - zipfile.ZIP_DEFLATED, - ) - - for invoice in invoices.with_context(progress_iter=True): - # create xml file for invoice - xml_invoice_data = self.generate_xml_invoice(invoice, check_xsd) - zip_file.writestr( - invoice.datev_filename(".xml"), - xml_invoice_data[1], - zipfile.ZIP_DEFLATED, + + package_limit = self.env.company.datev_package_limit * 1024 * 1024 + included = invoices.browse() + + buf = io.BytesIO() + zip_file = zipfile.ZipFile(buf, mode="w", compression=zipfile.ZIP_DEFLATED) + + for invoice in invoices.with_context(progress_iter=True): + # create xml file for invoice + xml_invoice_data = self.generate_xml_invoice(invoice, check_xsd) + zip_file.writestr(invoice.datev_filename(".xml"), xml_invoice_data[1]) + + # attach pdf file for vendor bills + attachment = self.generate_pdf(invoice) + if attachment: + zip_file.writestr(invoice.datev_filename(), attachment) + + included |= invoice + + # The file can grow slightly bigger than the limit + if buf.tell() > package_limit: + # Finalize the file + zip_file.writestr(*self.generate_xml_document(included, check_xsd)) + zip_file.close() + + yield base64.b64encode(buf.getvalue()), included + + # Open next zip and increment + buf = io.BytesIO() + included = invoices.browse() + zip_file = zipfile.ZipFile( + buf, mode="w", compression=zipfile.ZIP_DEFLATED ) - # attach pdf file for vendor bills - attachment = self.generate_pdf(invoice) - if attachment: - zip_file.writestr( - invoice.datev_filename(), - attachment, - zipfile.ZIP_DEFLATED, - ) + # Prozess the las chunk + if included: + zip_file.writestr(*self.generate_xml_document(included, check_xsd)) zip_file.close() - return base64.b64encode(s.getvalue()) + + yield base64.b64encode(buf.getvalue()), included diff --git a/datev_export_xml/models/res_company.py b/datev_export_xml/models/res_company.py index da0c4dd4..aaacb4c0 100644 --- a/datev_export_xml/models/res_company.py +++ b/datev_export_xml/models/res_company.py @@ -49,3 +49,17 @@ class ResCompany(models.Model): default="odoo", required=True, ) + + datev_package_limit = fields.Integer( + string="Package Limit in MB", + default=100, + ) + + _sql_constraints = [ + ( + "check_package_limit", + # DATEV only allows 465 MB and we leave some space + "CHECK(datev_package_limit >= 20 AND datev_package_limit <= 400)", + _("Package Limit for DATEV must be between 20MB and 400MB"), + ) + ] diff --git a/datev_export_xml/models/res_config_settings.py b/datev_export_xml/models/res_config_settings.py index 23157e85..785e00a3 100644 --- a/datev_export_xml/models/res_config_settings.py +++ b/datev_export_xml/models/res_config_settings.py @@ -32,3 +32,8 @@ class ResConfigSettings(models.TransientModel): related="company_id.datev_customer_order_ref", readonly=False, ) + + datev_package_limit = fields.Integer( + related="company_id.datev_package_limit", + readonly=False, + ) diff --git a/datev_export_xml/security/ir.model.access.csv b/datev_export_xml/security/ir.model.access.csv index 9ae7ee95..738ce51a 100644 --- a/datev_export_xml/security/ir.model.access.csv +++ b/datev_export_xml/security/ir.model.access.csv @@ -1,2 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_datev_export_xml,access_datev_export_xml,model_datev_export_xml,account.group_account_manager,1,1,1,1 +access_datev_export_xml_line,access_datev_export_xml_line,model_datev_export_xml_line,account.group_account_manager,1,1,0,1 diff --git a/datev_export_xml/tests/test_datev_export.py b/datev_export_xml/tests/test_datev_export.py index 897a5128..5ce34738 100644 --- a/datev_export_xml/tests/test_datev_export.py +++ b/datev_export_xml/tests/test_datev_export.py @@ -80,21 +80,21 @@ def setUpClass(cls): def _check_filecontent(self, export): # check content only for single invoice # datev export based on unit test cases - if not export.attachment_id or not export.invoice_ids: + if not export.line_ids.attachment_id or not export.invoice_ids: return {} export.check_valid_data(export.invoice_ids) invoice = export.invoice_ids[0].name invoice = invoice.replace("/", "-") - zip_data = base64.b64decode(export.attachment_id.datas) + zip_data = base64.b64decode(export.line_ids.attachment_id.datas) fp = io.BytesIO() fp.write(zip_data) zipfile.is_zipfile(fp) - file_list = [] + file_list = set() invoice_xml = {} with zipfile.ZipFile(fp, "r") as z: for zf in z.namelist(): - file_list.append(zf) + file_list.add(zf) doc_file = "document.xml" inv_file = str(invoice + ".xml") @@ -373,7 +373,7 @@ def _run_test_out_refund_datev_export(self, refund): start_date = refund.invoice_date end_date = refund.invoice_date_due datev_export = self.create_customer_datev_export(start_date, end_date) - self.assertFalse(datev_export.attachment_id) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -394,12 +394,12 @@ def _run_test_out_refund_datev_export(self, refund): self.assertEqual(datev_export.state, "pending") datev_export.with_user(datev_export.create_uid.id).get_zip() datev_export._create_activity() - datev_export._compute_datev_filesize() - self.assertTrue(datev_export.datev_filesize) + datev_export.line_ids._compute_filesize() + self.assertTrue(datev_export.line_ids.filesize) self.assertEqual(datev_export.state, "done") - self.assertTrue(datev_export.attachment_id) - file_list = ["document.xml", inv_number + ".xml", inv_number + ".pdf"] + self.assertTrue(datev_export.line_ids.attachment_id) + file_list = {"document.xml", inv_number + ".xml", inv_number + ".pdf"} res = self._check_filecontent(datev_export) # check list of files self.assertEqual(res["file_list"], file_list) @@ -423,7 +423,7 @@ def _run_test_in_refund_datev_export(self, refund, attachment): datev_export = self.create_vendor_datev_export(start_date, end_date) attachment = self.update_attachment(attachment, refund) - self.assertFalse(datev_export.attachment_id) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -448,8 +448,8 @@ def _run_test_in_refund_datev_export(self, refund, attachment): # self.DatevExportObj.refresh() self.assertEqual(datev_export.state, "done") - self.assertTrue(datev_export.attachment_id) - file_list = ["document.xml", f"{inv_number}.xml", f"{inv_number}.pdf"] + self.assertTrue(datev_export.line_ids.attachment_id) + file_list = {"document.xml", f"{inv_number}.xml", f"{inv_number}.pdf"} res = self._check_filecontent(datev_export) # check list of files self.assertEqual(res["file_list"], file_list) @@ -471,7 +471,7 @@ def _run_test_out_invoice_datev_export(self, invoice): start_date = invoice.invoice_date end_date = invoice.invoice_date_due datev_export = self.create_customer_datev_export(start_date, end_date) - self.assertFalse(datev_export.attachment_id) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -496,8 +496,8 @@ def _run_test_out_invoice_datev_export(self, invoice): # self.DatevExportObj.refresh() self.assertEqual(datev_export.state, "done") - self.assertTrue(datev_export.attachment_id) - file_list = ["document.xml", inv_number + ".xml", inv_number + ".pdf"] + self.assertTrue(datev_export.line_ids.attachment_id) + file_list = {"document.xml", inv_number + ".xml", inv_number + ".pdf"} res = self._check_filecontent(datev_export) # check list of files self.assertEqual(res["file_list"], file_list) @@ -521,7 +521,7 @@ def _run_test_in_invoice_datev_export(self, invoice, attachment): datev_export = self.create_vendor_datev_export(start_date, end_date) attachment = self.update_attachment(attachment, invoice) - self.assertFalse(datev_export.attachment_id) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -545,8 +545,8 @@ def _run_test_in_invoice_datev_export(self, invoice, attachment): # self.DatevExportObj.refresh() self.assertEqual(datev_export.state, "done") - self.assertTrue(datev_export.attachment_id) - file_list = ["document.xml", f"{inv_number}.xml", f"{inv_number}.pdf"] + self.assertTrue(datev_export.line_ids.attachment_id) + file_list = {"document.xml", f"{inv_number}.xml", f"{inv_number}.pdf"} res = self._check_filecontent(datev_export) # check list of files self.assertEqual(res["file_list"], file_list) @@ -566,7 +566,7 @@ def _run_test_out_inv_datev_export_manually(self, invoice): self.assertEqual(invoice.state, "posted") datev_export = self.create_customer_datev_export_manually(invoice) - self.assertFalse(datev_export.attachment_id) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -589,8 +589,8 @@ def _run_test_out_inv_datev_export_manually(self, invoice): datev_mode="datev_export" ).export_zip() self.assertEqual(datev_export.state, "done") - self.assertTrue(datev_export.attachment_id) - file_list = ["document.xml", inv_number + ".xml", inv_number + ".pdf"] + self.assertTrue(datev_export.line_ids.attachment_id) + file_list = {"document.xml", inv_number + ".xml", inv_number + ".pdf"} res = self._check_filecontent(datev_export) # check list of files self.assertEqual(res["file_list"], file_list) @@ -744,7 +744,7 @@ def test_14_datev_export_without_invoice(self): "export_type": "out", } ) - self.assertFalse(datev_export.attachment_id) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, diff --git a/datev_export_xml/views/datev_export_views.xml b/datev_export_xml/views/datev_export_views.xml index 137ac7b2..98559030 100644 --- a/datev_export_xml/views/datev_export_views.xml +++ b/datev_export_xml/views/datev_export_views.xml @@ -118,7 +118,7 @@ name="export_zip" type="object" class="oe_highlight" - attrs="{'invisible': ['|', ('attachment_id', '!=', False), ('manually_document_selection', '=', False)]}" + attrs="{'invisible': ['|', ('line_count', '>', 0), ('manually_document_selection', '=', False)]}" confirm="The creation of the file can take some time! Do you really want to proceed!" /> - - - -
-