diff --git a/datev_export_xml/__init__.py b/datev_export_xml/__init__.py index 2d0d4cbc..3d5d4bd0 100644 --- a/datev_export_xml/__init__.py +++ b/datev_export_xml/__init__.py @@ -7,4 +7,4 @@ # @author Grzegorz Grzelak # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from . import models +from . import controllers, models diff --git a/datev_export_xml/__manifest__.py b/datev_export_xml/__manifest__.py index 8d1d38b1..5b5b3d2d 100644 --- a/datev_export_xml/__manifest__.py +++ b/datev_export_xml/__manifest__.py @@ -9,7 +9,7 @@ { "name": "Datev Export XML", - "version": "14.0.1.0.3", + "version": "14.0.1.1.3", "category": "Accounting", "license": "AGPL-3", "author": "Guenter Selbert, Thorsten Vocks, Maciej Wichowski, Daniela Scarpa, " diff --git a/datev_export_xml/controllers/__init__.py b/datev_export_xml/controllers/__init__.py new file mode 100644 index 00000000..701c498f --- /dev/null +++ b/datev_export_xml/controllers/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2022-2024 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import main diff --git a/datev_export_xml/controllers/main.py b/datev_export_xml/controllers/main.py new file mode 100644 index 00000000..4cd2e2e9 --- /dev/null +++ b/datev_export_xml/controllers/main.py @@ -0,0 +1,40 @@ +# Copyright (C) 2022-2024 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +import logging + +from odoo import http +from odoo.http import request, send_file + +from odoo.addons.web.controllers.main import Home + +_logger = logging.getLogger(__name__) + + +class DatevHome(Home): + @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() + + att = export.attachment_id + + if att.store_fname: + full_path = att._full_path(att.store_fname) + return send_file( + full_path, + filename=att.name, + mimetype=att.mimetype, + as_attachment=True, + ) + + return request.make_response( + base64.b64decode(att.datas), + [ + ("Content-Type", att.mimetype), + ("Content-Disposition", f'attachment; filename="{att.name}"'), + ], + ) diff --git a/datev_export_xml/i18n/de.po b/datev_export_xml/i18n/de.po index 2ba121ba..d9a0fbc1 100644 --- a/datev_export_xml/i18n/de.po +++ b/datev_export_xml/i18n/de.po @@ -398,13 +398,13 @@ msgstr "Dateigröße" #: code:addons/datev_export_xml/models/datev_export.py:0 #, python-format msgid "" -"Filtered Export of {} Documents\n" -"Date Range: {}-{}\n" -"Types: {}" +"Filtered Export of %(count)s Documents\n" +"Date Range: %(start)s-%(stop)s\n" +"Types: %(types)s" msgstr "" -"Gefilterter Export von {} Dokumente\n" -"Datumsbereich: {}-{}\n" -"Typen: {}" +"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 @@ -567,11 +567,11 @@ msgstr "" #: code:addons/datev_export_xml/models/datev_export.py:0 #, python-format msgid "" -"Manually Doc Export of {} Documents \n" -"Numbers: {}" +"Manual Export of %(count)s Documents\n" +"Numbers: %(names)s" msgstr "" -"Manueller Export von {} Documenten \n" -"Nummern: {}" +"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/14.0.1.1.3/post-migrate.py b/datev_export_xml/migrations/14.0.1.1.3/post-migrate.py new file mode 100644 index 00000000..0956f79c --- /dev/null +++ b/datev_export_xml/migrations/14.0.1.1.3/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 299ba0e8..82b5f8ce 100644 --- a/datev_export_xml/models/__init__.py +++ b/datev_export_xml/models/__init__.py @@ -17,4 +17,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 14846da6..9d958259 100644 --- a/datev_export_xml/models/datev_export.py +++ b/datev_export_xml/models/datev_export.py @@ -109,24 +109,22 @@ 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_file = fields.Binary("ZIP file", readonly=True, related="attachment_id.datas") - 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( - string="Invoices Count", compute="_compute_invoices_count", store=True + 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) @@ -147,22 +145,23 @@ def name_get(self): tracking=True, ) - @api.depends("attachment_id", "attachment_id.datas") - def _compute_datev_filesize(self): - for r in self.with_context(bin_size=True): - r.datev_filesize = r.datev_file + @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): @@ -215,50 +214,66 @@ 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 {} Documents\nDate Range: {}-{}\nTypes: {}" - ).format( - len(self.invoice_ids), - self.date_start, - self.date_stop, - ", ".join(self.get_type_list()), - ) - else: - description = _( - "Manually Doc Export of {} Documents \nNumbers: {}" - ).format( - len(self.invoice_ids), - ", ".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, + "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() @@ -273,14 +288,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() @@ -291,7 +317,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): @@ -357,6 +382,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: @@ -381,13 +409,6 @@ def action_pending(self): r.invoice_ids = [(6, 0, r.get_invoices().ids)] if r.invoices_count == 0: raise ValidationError(_("No invoices/refunds for export!")) - if r.invoices_count > 4999 and r.check_xsd: - raise ValidationError( - _( - "The numbers of invoices/refunds is limited to 4999 by DATEV! " - "Please decrease the number of documents or deactivate Check XSD." - ) - ) if r.state == "running": raise ValidationError( _("It's not allowed to set an already running export to pending!") @@ -400,12 +421,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") @@ -450,6 +471,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_xml_generator.py b/datev_export_xml/models/datev_xml_generator.py index 82ef2647..afd20a31 100644 --- a/datev_export_xml/models/datev_xml_generator.py +++ b/datev_export_xml/models/datev_xml_generator.py @@ -83,7 +83,9 @@ def generate_xml_document(self, invoices, check_xsd=True): "Document_v050.xsd", ) - return "document.xml", etree.tostring(root) + return "document.xml", etree.tostring( + root, xml_declaration=True, encoding="UTF-8" + ) @api.model def generate_xml_invoice(self, invoice, check_xsd=True): @@ -102,4 +104,4 @@ def generate_xml_invoice(self, invoice, check_xsd=True): invoice=invoice, ) - return doc_name, etree.tostring(root) + return doc_name, etree.tostring(root, xml_declaration=True, encoding="UTF-8") diff --git a/datev_export_xml/models/datev_zip_generator.py b/datev_export_xml/models/datev_zip_generator.py index af476afb..52446b16 100644 --- a/datev_export_xml/models/datev_zip_generator.py +++ b/datev_export_xml/models/datev_zip_generator.py @@ -41,31 +41,51 @@ 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() + file_counter = 0 + + 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]) + file_counter += 1 + + # attach pdf file for vendor bills + attachment = self.generate_pdf(invoice) + if attachment: + zip_file.writestr(invoice.datev_filename(), attachment) + file_counter += 1 + + included |= invoice + + # The file can grow slightly bigger than the limit + if buf.tell() > package_limit or file_counter >= 4800: + # 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 + file_counter = 0 + 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 c604e0b0..48f3c0cb 100644 --- a/datev_export_xml/models/res_company.py +++ b/datev_export_xml/models/res_company.py @@ -50,3 +50,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 7c3c9fcf..58cf3780 100644 --- a/datev_export_xml/tests/test_datev_export.py +++ b/datev_export_xml/tests/test_datev_export.py @@ -87,30 +87,30 @@ def setUp(self): 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.datev_file) + 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") doc_data = z.read(doc_file) inv_data = z.read(inv_file) # document.xml - doc_root = etree.fromstring(doc_data.decode("utf-8")) + doc_root = etree.fromstring(doc_data) # invoice.xml file - inv_root = etree.fromstring(inv_data.decode("utf-8")) + inv_root = etree.fromstring(inv_data) for i in inv_root: invoice_xml.update(i.attrib) @@ -376,7 +376,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.assertEqual(datev_export.datev_file, False) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -397,13 +397,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.datev_file) - 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) @@ -427,7 +426,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.assertEqual(datev_export.datev_file, False) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -452,9 +451,8 @@ def _run_test_in_refund_datev_export(self, refund, attachment): # self.DatevExportObj.refresh() self.assertEqual(datev_export.state, "done") - self.assertTrue(datev_export.datev_file) - 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) @@ -476,7 +474,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.assertEqual(datev_export.datev_file, False) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -501,9 +499,8 @@ def _run_test_out_invoice_datev_export(self, invoice): # self.DatevExportObj.refresh() self.assertEqual(datev_export.state, "done") - self.assertTrue(datev_export.datev_file) - 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) @@ -527,7 +524,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.assertEqual(datev_export.datev_file, False) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -551,9 +548,8 @@ def _run_test_in_invoice_datev_export(self, invoice, attachment): # self.DatevExportObj.refresh() self.assertEqual(datev_export.state, "done") - self.assertTrue(datev_export.datev_file) - 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) @@ -573,7 +569,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.assertEqual(datev_export.datev_file, False) + self.assertFalse(datev_export.line_ids.attachment_id) self.assertEqual( datev_export.client_number, self.env.company.datev_client_number, @@ -596,9 +592,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.datev_file) - 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) @@ -752,7 +747,7 @@ def test_14_datev_export_without_invoice(self): "export_type": "out", } ) - self.assertEqual(datev_export.datev_file, False) + 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 c8b5d320..fd6cc8a8 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': ['|', ('datev_file', '!=', 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!" />