From 08633bd22dc8b9f73bfab0e9f28f4956ae843fd3 Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Thu, 4 Jul 2024 21:23:57 +0100 Subject: [PATCH] [FIX] account_factoring_receivable_balance_factofrance --- .../views/subrogation_receipt.xml | 2 +- .../static/description/index.html | 11 +- .../data/subrogation_seq.xml | 4 +- .../models/subrogation_receipt.py | 239 +++++++++--------- .../static/description/index.html | 11 +- .../views/subrogation_receipt_views.xml | 37 +-- 6 files changed, 148 insertions(+), 156 deletions(-) diff --git a/account_factoring_receivable_balance/views/subrogation_receipt.xml b/account_factoring_receivable_balance/views/subrogation_receipt.xml index de78f7be03..6ae36c323a 100644 --- a/account_factoring_receivable_balance/views/subrogation_receipt.xml +++ b/account_factoring_receivable_balance/views/subrogation_receipt.xml @@ -99,7 +99,7 @@ name="journal_id" options="{"no_open":True}" /> - + diff --git a/account_factoring_receivable_balance_bpce/static/description/index.html b/account_factoring_receivable_balance_bpce/static/description/index.html index e12ca015a6..b863934363 100644 --- a/account_factoring_receivable_balance_bpce/static/description/index.html +++ b/account_factoring_receivable_balance_bpce/static/description/index.html @@ -8,11 +8,10 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ +:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. -Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -275,7 +274,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: gray; } /* line numbers */ +pre.code .ln { color: grey; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -301,7 +300,7 @@ span.pre { white-space: pre } -span.problematic, pre.problematic { +span.problematic { color: red } span.section-subtitle { @@ -444,9 +443,7 @@

Contributors

Maintainers

This module is maintained by the OCA.

- -Odoo Community Association - +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.

diff --git a/account_factoring_receivable_balance_factofrance/data/subrogation_seq.xml b/account_factoring_receivable_balance_factofrance/data/subrogation_seq.xml index 6bca09c9c8..b0bb0daa3f 100644 --- a/account_factoring_receivable_balance_factofrance/data/subrogation_seq.xml +++ b/account_factoring_receivable_balance_factofrance/data/subrogation_seq.xml @@ -2,7 +2,7 @@ Factoring Receivable factoring.receivable - 5 + 3 - \ No newline at end of file + diff --git a/account_factoring_receivable_balance_factofrance/models/subrogation_receipt.py b/account_factoring_receivable_balance_factofrance/models/subrogation_receipt.py index d81bc9dc50..895e7e4178 100644 --- a/account_factoring_receivable_balance_factofrance/models/subrogation_receipt.py +++ b/account_factoring_receivable_balance_factofrance/models/subrogation_receipt.py @@ -3,10 +3,9 @@ import base64 import re -from odoo import Command, _, api, fields, models, tools +from odoo import Command, _, api, fields, models from odoo.exceptions import UserError -FORMAT_VERSION = "7.0" RETURN = "\r\n" @@ -55,14 +54,17 @@ def _prepare_factor_file_factofrance(self): } def action_confirm(self): - if self.name == _("New"): - self.name = self.env["ir.sequence"].next_by_code("factoring.receivable") - super().action_confirm() + for rec in self: + if rec.name == _("New"): + rec.name = self.env["ir.sequence"].next_by_code("factoring.receivable") + res = super().action_confirm() + self.write({"state": "posted"}) + return res def action_draft(self): - self.state = "draft" + self.write({"state": "draft"}) self.env["ir.attachment"].search( - [("res_id", "=", self.id), ("res_model", "=", self._name)] + [("res_id", "in", self.ids), ("res_model", "=", self._name)] ).unlink() def _prepare_factor_file_data_factofrance(self): @@ -80,22 +82,13 @@ def _prepare_factor_file_data_factofrance(self): header = self._get_factofrance_header() # check_column_size(header) - body, max_row, balance = self._get_factofrance_body() - ender = self._get_factofrance_ender(max_row, balance) + rows, totals = self._prepare_factofrance_body() + body = self._get_factofrance_body(rows) + ender = self._get_factofrance_ender(totals) + self.balance = totals["balance"] # check_column_size(ender) - raw_data = f"{header}{RETURN}{body}{RETURN}{ender}{RETURN}".replace( - "False", " " - ) - # data = clean_string(raw_data) - data = raw_data - dev_mode = tools.config.options.get("dev_mode") - if dev_mode and dev_mode[0][-3:] == "pdb" or False: - # make debugging easier saving file on filesystem to check - debug(raw_data, "_raw") - debug(data) - # pylint: disable=C8107 - raise UserError("See files /odoo/subrog*.txt") - self.write({"balance": balance}) + raw_data = RETURN.join([header, body, ender]) + data = clean_string(raw_data) # non ascii chars are replaced data = bytes(data, "ascii", "replace").replace(b"?", b" ") return base64.b64encode(data) @@ -105,73 +98,54 @@ def _get_factofrance_header(self): factor_code = str(self.factor_journal_id.factor_code).ljust(4, " ") company_name = self.company_id.name.ljust(40, " ") - create_date = factofrance_date(self.create_date).ljust(8, " ") - identification_type = "1".ljust(1, " ") - country_codes = "2".ljust(1, " ") - company_identifiers = (self.company_id.siret or self.company_id.vat).ljust( - 14, " " + confirm_date = factofrance_date(self.date) + identification_type = "1" + country_codes = "2" + company_identifiers = get_column( + self.company_id.siret or self.company_id.vat, 14 ) - file_sequence_number = str(self.id).ljust(16, " ") + file_sequence_number = ("000" + self.name)[-3:] currency = self.company_id.currency_id.name.ljust(3, " ") operation_type = "000000".ljust(6, " ") # Create a list with the header parts, initially filled with spaces header_parts = [" "] * 360 - - # Insert each value at the specified position header_parts[0:2] = "100" - header_parts[ - 3:7 - ] = factor_code # Position 4 (0-based index 3) to 7 (not inclusive) + header_parts[3:7] = factor_code # Position 4 to 7 (not inclusive) header_parts[9:49] = company_name # Position 10 to 49 - header_parts[49:56] = create_date # Position 50 to 56 + header_parts[49:56] = confirm_date # Position 50 to 56 header_parts[117] = identification_type # Position 118 header_parts[118] = country_codes # Position 119 header_parts[119:133] = company_identifiers # Position 120 to 133 - header_parts[135:151] = file_sequence_number # Position 136 to 151 - header_parts[351:354] = currency # Position 352 to 354 + header_parts[135:137] = file_sequence_number # Position 136 to 139 + header_parts[351:353] = currency # Position 352 to 354 header_parts[354:360] = operation_type # Position 355 to 360 # Join the list into a single string header = "".join(header_parts) - return header - # return string.format(**info) - - def _get_factofrance_ender(self, max_row, balance): - self = self.sudo() - invoices = self.item_ids.filtered( - lambda line: not line.is_refund - and line.move_id.move_type in ("out_invoice") - ) - credit_notes = self.item_ids.filtered( - lambda line: line.is_refund and line.move_id.move_type in ("out_refund") - ) + def _get_factofrance_ender(self, totals): info = { "code": "199", "factor_code": str(self.factor_journal_id.factor_code), "company_name": self.company_id.name, - "create_date": factofrance_date(self.create_date), - # "number_of_invoices": str(len(invoices.mapped("move_id"))), - # "total_amount_of_invoices": pad(sum(invoices.mapped("move_id.amount_total")), ), - # "number_of_credit_notes": len(credit_notes.mapped("move_id")), - # "total_amount_of_credit_notes": sum( - # credit_notes.mapped("move_id.amount_total") - # ), - # "number_of_payments": len(self.item_ids.mapped("move_id")), - # "total_amount_of_payments": sum( - # self.item_ids.mapped("move_id.amount_total") - # ), - # "currency": self.company_id.currency_id.name, + "confirm_date": factofrance_date(self.date), + "currency": self.company_id.currency_id.name, "operation_type": "000000", } end_parts = [" "] * 360 end_parts[0:2] = info.get("code") - end_parts[2:8] = info.get("factor_code") + end_parts[3:8] = info.get("factor_code") end_parts[9:49] = info.get("company_name") - end_parts[49:57] = info.get("create_date") - end_parts[58:353] = "".ljust(395, " ") + end_parts[49:56] = info.get("confirm_date") + end_parts[57:60] = get_column(totals["number_of_invoices"], 4) + end_parts[61:75] = get_column(totals["total_amount_of_invoices"], 15) + end_parts[76:79] = get_column(totals["number_of_credit_notes"], 4) + end_parts[80:94] = get_column(totals["total_amount_of_credit_notes"], 15) + end_parts[95:98] = get_column(totals["number_of_payments"], 4) + end_parts[99:113] = get_column(totals["total_amount_of_payments"], 15) + end_parts[351:353] = info.get("currency") end_parts[354:357] = info.get("operation_type") # Join the list into a single string end = "".join(end_parts) @@ -219,51 +193,65 @@ def action_compute_lines(self): amls.write({"subrogation_id": self.id}) return res - def _get_factofrance_body(self): - self = self.sudo() + def _prepare_factofrance_body(self): rows = [] - balance = 0 - lines = self.line_ids - for line in lines: + totals = { + "number_of_invoices": 0, + "total_amount_of_invoices": 0.00, + "number_of_credit_notes": 0, + "total_amount_of_credit_notes": 0.00, + "number_of_payments": 0, + "total_amount_of_payments": 0.00, + "balance": 0.00, + } + for line in self.item_ids: move = line.move_id partner = line.move_id.partner_id.commercial_partner_id if not partner: raise UserError(f"Pas de partenaire sur la pièce {line.move_id}") - total = move.amount_total_in_currency_signed + + code = self.get_code(move, line) + amount = line.debit or line.credit + if code == "101": + totals["number_of_invoices"] += 1 + totals["total_amount_of_invoices"] += amount + elif code == "102": + totals["number_of_credit_notes"] += 1 + totals["total_amount_of_credit_notes"] += amount + elif code == "103": + totals["number_of_payments"] += 1 + totals["total_amount_of_payments"] += amount + totals["balance"] += line.balance info = { - "code": str(self.get_code(move, line)) or " ", - "create_date": factofrance_date(move.create_date) or " ", + "code": code or " ", + "confirm_date": factofrance_date(self.date), "factor_code": str(self.factor_journal_id.factor_code) or " ", - "company_identifiers": self.company_id.siret - or self.company_id.vat - or " ", - "document": "siret" if partner.siret else "vat", + "document": partner.siret or partner.vat or "0", "document2": " ", - "customer_name": partner.name - and partner.name.ljust(40, " ") - or "".ljust(40, " "), - "customer_street": partner.street - and partner.street.ljust(40, " ") - or " ", - "customer_street2": partner.street2 - and partner.street2.ljust(40, " ") - or "".ljust(40, " "), + "customer_name": (partner.name or " ").ljust(40, " "), + "customer_address": " ".join( + x for x in [partner.street, partner.street2, partner.street3] if x + ), + # "customer_street": partner.street + # and partner.street.ljust(40, " ") + # or " ", + # "customer_street2": partner.street2 + # and partner.street2.ljust(40, " ") + # or "".ljust(40, " "), "customer_zip_code": partner.zip or " ", - "delivery_office": " ", + "delivery_office": partner.city or " ", "customer_country_code": partner.country_id.code or " ", "customer_phone": partner.mobile or partner.phone or " ", "customer_ref": partner.ref and partner.ref.ljust(10, " ") or "".ljust(10, " "), - "date": factofrance_date(move.invoice_date or move.date) or " ", - "number": move.name[:15].ljust(15, " "), + "date": factofrance_date(move.invoice_date or move.date), + "number": get_column(move.name.replace("/", ""), 15), "currency": self.company_id.currency_id.name or " ", "account_sign": "+" if move.move_type == "out_invoice" else "-", - "amount": (str(line.debit or line.credit).replace(".", "")).rjust( - 15, "0" - ) - or " ", + "amount": (str(amount).replace(".", "")).rjust(15, "0") or " ", + "payment_mode": "VIR", "invoice_date_due": factofrance_date(move.invoice_date_due) if move.move_type == "out_invoice" else "".ljust(11, " ") or " ", @@ -272,15 +260,22 @@ def _get_factofrance_body(self): or "".ljust(40, " "), "operation_type": get_type_piece(move, line) or " ", } - balance += total + rows.append(info) + return rows, totals + + def _get_factofrance_body(self, rows_info): + rows = [] + for info in rows_info: line = [" "] * 361 line[0:2] = info.get("code") - line[3:10] = info.get("create_date") - line[17:24] = info.get("document") + line[3:10] = factofrance_date(self.date) + line[11:16] = info.get("factor_code") + line[17:25] = info.get("document") line[26:30] = info.get("document2") line[31:70] = info.get("customer_name") - line[111:150] = info.get("customer_street") - line[151:190] = info.get("customer_street2") + line[111:190] = info.get("customer_address") + # line[111:150] = info.get("customer_street") + # line[151:190] = info.get("customer_street2") line[191:196] = info.get("customer_zip_code") line[197:230] = info.get("delivery_office") line[231:233] = info.get("customer_country_code") @@ -291,27 +286,17 @@ def _get_factofrance_body(self): line[277:279] = info.get("currency") line[280:280] = info.get("account_sign") line[281:295] = info.get("amount") - if move.move_type == "out_invoice": - line[299:306] = info.get("invoice_date_due") - else: - line[297:306] = info.get("invoice_date_due") - line[317:356] = info.get("customer_order_reference") + line[296:298] = info.get("payment_mode") + line[299:306] = info.get("invoice_date_due") + line[307:316] = info.get("customer_order_reference") line[357:360] = info.get("operation_type") formatted_string = "".join(line) - rows.append(formatted_string) - return (RETURN.join(rows), len(rows), balance) - - -def get_piece_factor(name, p_type): - if not p_type: - return "{}{}".format(name[:15], pad(" ", 15)) - return name[:30] + return RETURN.join(rows) def get_type_piece(move, line): - journal_type = move.journal_id.type p_type = " " move_type = move.move_type if move_type == "entry": @@ -326,28 +311,38 @@ def get_type_piece(move, line): return p_type -def factofrance_date(date_field): - return date_field.strftime("%Y%m%d") +def get_column(value, size): + if isinstance(value, int): + if value >= pow(10, size): + res = "9" * size + else: + res = str(value or 0).rjust(size, "0") + res = res[-size:] + elif isinstance(value, float): + if value * 100 >= pow(10, size): + res = "9" * size + else: + res = str(round(value * 100) or 0).rjust(size, "0") + res = res[-size:] + else: + res = str(value).ljust(size, " ") + res = res[:size] + return res + + +def factofrance_date(date_value): + return date_value.strftime("%Y%m%d") if date_value else " " def clean_string(string): """Remove all except [A-Z], space, \r, \n https://www.rapidtables.com/code/text/ascii-table.html""" - string = string.replace(FORMAT_VERSION, "FORMATVERSION") string = string.upper() string = re.sub(r"[\x21-\x2F]|[\x3A-\x40]|[\x5E-\x7F]|\x0A\x0D", r" ", string) - string = string.replace("FORMATVERSION", FORMAT_VERSION) + string = string.replace("False", " ") return string -def debug(content, suffix=""): - mpath = f"/odoo/subrog{suffix}.txt" - with open(mpath, "wb") as f: - if isinstance(content, str): - content = bytes(content, "ascii", "replace") - f.write(content) - - # def check_column_size(string, fstring=None, info=None): # print("\n string", string) # if len(string) != 358: diff --git a/account_factoring_receivable_balance_factofrance/static/description/index.html b/account_factoring_receivable_balance_factofrance/static/description/index.html index 5b586d9bb0..f401d54356 100644 --- a/account_factoring_receivable_balance_factofrance/static/description/index.html +++ b/account_factoring_receivable_balance_factofrance/static/description/index.html @@ -8,11 +8,10 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ +:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. -Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -275,7 +274,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: gray; } /* line numbers */ +pre.code .ln { color: grey; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -301,7 +300,7 @@ span.pre { white-space: pre } -span.problematic, pre.problematic { +span.problematic { color: red } span.section-subtitle { @@ -444,9 +443,7 @@

Contributors

Maintainers

This module is maintained by the OCA.

- -Odoo Community Association - +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.

diff --git a/account_factoring_receivable_balance_factofrance/views/subrogation_receipt_views.xml b/account_factoring_receivable_balance_factofrance/views/subrogation_receipt_views.xml index bccb197ccd..785bf1f9b1 100644 --- a/account_factoring_receivable_balance_factofrance/views/subrogation_receipt_views.xml +++ b/account_factoring_receivable_balance_factofrance/views/subrogation_receipt_views.xml @@ -1,26 +1,29 @@ - - subrogation_receipt_form_inherit + + subrogation_receipt_form_inherit subrogation.receipt - + - -