diff --git a/l10n_do_accounting/__manifest__.py b/l10n_do_accounting/__manifest__.py index e8698541c..f46343b22 100644 --- a/l10n_do_accounting/__manifest__.py +++ b/l10n_do_accounting/__manifest__.py @@ -9,6 +9,7 @@ "license": "LGPL-3", "website": "https://github.com/odoo-dominicana", "version": "17.0.1.0.0", + "countries": ["do"], # any module necessary for this one to work correctly "depends": ["l10n_latam_invoice_document", "l10n_do"], # always loaded diff --git a/l10n_do_accounting/data/l10n_latam.document.type.csv b/l10n_do_accounting/data/l10n_latam.document.type.csv index 87d0794b4..6863f5695 100644 --- a/l10n_do_accounting/data/l10n_latam.document.type.csv +++ b/l10n_do_accounting/data/l10n_latam.document.type.csv @@ -1,23 +1,23 @@ -id,sequence,code,name,report_name,internal_type,l10n_do_ncf_type,doc_code_prefix,country_id/id,is_vat_required,active -ncf_fiscal_client,10,B,Crédito Fiscal,Factura de Crédito Fiscal,invoice,01,B01,base.do,True,TRUE -ncf_consumer_supplier,30,B,Consumo,Factura de Consumo,invoice,02,B02,base.do,False,TRUE -ncf_debit_note_client,40,B,Nota de Débito,Nota de Débito,debit_note,03,B03,base.do,True,TRUE -ncf_credit_note_client,60,B,Nota de Crédito,Nota de Crédito,credit_note,04,B04,base.do,False,TRUE -ncf_informal_supplier,80,B,Comprobante de Compra,Factura de Compra,invoice,11,B11,base.do,True,TRUE -ncf_unique_client,90,B,Único Ingreso,Factura de Único Ingreso,invoice,12,B12,base.do,False,TRUE -ncf_minor_supplier,100,B,Gasto Menor,Gasto Menor,invoice,13,B13,base.do,False,TRUE -ncf_special_client,110,B,Régimen Especial,Factura de Régimen Especial,invoice,14,B14,base.do,True,TRUE -ncf_gov_client,120,B,Factura Gubernamental,Factura Gubernamental,invoice,15,B15,base.do,True,TRUE -ncf_export_client,130,B,Exportación,Factura de Exportación,invoice,16,B16,base.do,True,TRUE -ncf_exterior_supplier,140,B,Pago al Exterior,Factura de Pago al Exterior,invoice,17,B17,base.do,False,TRUE -non_fiscal_import_supplier,150,N,Importación,Factura de Importación,invoice,,IMP,base.do,False,FALSE -ecf_fiscal_client,160,E,Crédito Fiscal Electrónica,Factura de Crédito Fiscal Electrónica,invoice,31,E31,base.do,True,TRUE -ecf_consumer_supplier,170,E,Consumo Electrónica,Factura de Consumo Electrónica,invoice,32,E32,base.do,False,TRUE -ecf_debit_note_client,180,E,Nota de Débito Electrónica,Nota de Débito Electrónica,debit_note,33,E33,base.do,True,TRUE -ecf_credit_note_client,190,E,Nota de Crédito Electrónica,Nota de Crédito Electrónica,credit_note,34,E34,base.do,True,TRUE -ecf_informal_supplier,200,E,Compras Electrónica,Factura de Compra Electrónica,invoice,41,E41,base.do,True,TRUE -ecf_minor_supplier,220,E,Gasto Menor Electrónica,Factura Gasto Menor Electrónica,invoice,43,E43,base.do,False,TRUE -ecf_special_client,230,E,Régimen Especial Electrónica,Factura de Régimen Especial Electrónica,invoice,44,E44,base.do,True,TRUE -ecf_gov_client,240,E,Factura Gubernamental Electrónica,Factura Gubernamental Electrónica,invoice,45,E45,base.do,True,TRUE -ecf_export_client,250,E,Exportación Electrónica,Factura de Exportación Electrónica,invoice,46,E46,base.do,True,TRUE -ecf_exterior_supplier,260,E,Pago al Exterior Electrónica,Factura de Pago al Exterior Electrónica,invoice,47,E47,base.do,False,TRUE +id,sequence,code,name@es,name,report_name,internal_type,l10n_do_ncf_type,doc_code_prefix,country_id/id,is_vat_required,active +ncf_fiscal_client,10,B,Crédito Fiscal,Fiscal Credit,Factura de Crédito Fiscal,invoice,1,B01,base.do,TRUE,TRUE +ncf_consumer_supplier,30,B,Consumo,Consumer,Factura de Consumo,invoice,2,B02,base.do,FALSE,TRUE +ncf_debit_note_client,40,B,Nota de Débito,Debit Note,Nota de Débito,debit_note,3,B03,base.do,TRUE,TRUE +ncf_credit_note_client,60,B,Nota de Crédito,Credit Note,Nota de Crédito,credit_note,4,B04,base.do,FALSE,TRUE +ncf_informal_supplier,80,B,Comprobante de Compra,Purchase receipt,Factura de Compra,invoice,11,B11,base.do,TRUE,TRUE +ncf_unique_client,90,B,Único Ingreso,Single Income,Factura de Único Ingreso,invoice,12,B12,base.do,FALSE,TRUE +ncf_minor_supplier,100,B,Gasto Menor,Minor Expense,Gasto Menor,invoice,13,B13,base.do,FALSE,TRUE +ncf_special_client,110,B,Régimen Especial,Special Regime,Factura de Régimen Especial,invoice,14,B14,base.do,TRUE,TRUE +ncf_gov_client,120,B,Factura Gubernamental,Government Invoice,Factura Gubernamental,invoice,15,B15,base.do,TRUE,TRUE +ncf_export_client,130,B,Exportación,Export,Factura de Exportación,invoice,16,B16,base.do,TRUE,TRUE +ncf_exterior_supplier,140,B,Pago al Exterior,Foreign Payment,Factura de Pago al Exterior,invoice,17,B17,base.do,FALSE,TRUE +non_fiscal_import_supplier,150,N,Importación,Import,Factura de Importación,invoice,,IMP,base.do,FALSE,FALSE +ecf_fiscal_client,160,E,Crédito Fiscal Electrónica,Electronic Fiscal Credit,Factura de Crédito Fiscal Electrónica,invoice,31,E31,base.do,TRUE,TRUE +ecf_consumer_supplier,170,E,Consumo Electrónica,Electronic Consumer,Factura de Consumo Electrónica,invoice,32,E32,base.do,FALSE,TRUE +ecf_debit_note_client,180,E,Nota de Débito Electrónica,Electronic Debit Note,Nota de Débito Electrónica,debit_note,33,E33,base.do,TRUE,TRUE +ecf_credit_note_client,190,E,Nota de Crédito Electrónica,Electronic Credit Note,Nota de Crédito Electrónica,credit_note,34,E34,base.do,TRUE,TRUE +ecf_informal_supplier,200,E,Compras Electrónica,Electronic Purchase,Factura de Compra Electrónica,invoice,41,E41,base.do,TRUE,TRUE +ecf_minor_supplier,220,E,Gasto Menor Electrónica,Electronic Minor Expense,Factura Gasto Menor Electrónica,invoice,43,E43,base.do,FALSE,TRUE +ecf_special_client,230,E,Régimen Especial Electrónica,Electronic Special Regime,Factura de Régimen Especial Electrónica,invoice,44,E44,base.do,TRUE,TRUE +ecf_gov_client,240,E,Factura Gubernamental Electrónica,Electronic Government Invoice,Factura Gubernamental Electrónica,invoice,45,E45,base.do,TRUE,TRUE +ecf_export_client,250,E,Exportación Electrónica,Electronic Export,Factura de Exportación Electrónica,invoice,46,E46,base.do,TRUE,TRUE +ecf_exterior_supplier,260,E,Pago al Exterior Electrónica,Electronic Foreign Payment,Factura de Pago al Exterior Electrónica,invoice,47,E47,base.do,FALSE,TRUE \ No newline at end of file diff --git a/l10n_do_accounting/models/account_journal.py b/l10n_do_accounting/models/account_journal.py index 567f85cab..7810c9171 100644 --- a/l10n_do_accounting/models/account_journal.py +++ b/l10n_do_accounting/models/account_journal.py @@ -199,7 +199,7 @@ def create(self, vals_list): def write(self, values): to_check = {"type", "l10n_latam_use_documents"} - res = super().write(values) + res = super(AccountJournal, self).write(values) if to_check.intersection(set(values.keys())): for rec in self: rec._l10n_do_create_document_types() diff --git a/l10n_do_accounting/models/account_move.py b/l10n_do_accounting/models/account_move.py index 2c70da5d2..83b5efbc6 100644 --- a/l10n_do_accounting/models/account_move.py +++ b/l10n_do_accounting/models/account_move.py @@ -1,10 +1,10 @@ import re -from psycopg2 import sql from werkzeug import urls from odoo import models, fields, api, _ from odoo.osv import expression from odoo.exceptions import ValidationError, UserError, AccessError +from odoo.tools.sql import column_exists, create_column, drop_index, index_exists class AccountMove(models.Model): @@ -124,36 +124,79 @@ def _get_l10n_do_income_type(self): "manually because a new expiration date was set on journal", ) - def init(self): - super(AccountMove, self).init() - - if not self._abstract and self._sequence_index: - index_name = self._table + "_l10n_do_sequence_index" + _sql_constraints = [ + ( + "unique_l10n_do_fiscal_number_sales", + "", + "Another document with the same fiscal number already exists.", + ), + ( + "unique_l10n_do_fiscal_number_purchase_manual", + "", + "Another document for the same partner with the same fiscal number already exists.", + ), + ( + "unique_l10n_do_fiscal_number_purchase_internal", + "", + "Another document for the same partner with the same fiscal number already exists.", + ), + ] + + # def init(self): + # super(AccountMove, self).init() + # + # if not self._abstract and self._sequence_index: + # index_name = self._table + "_l10n_do_sequence_index" + # self.env.cr.execute( + # "SELECT indexname FROM pg_indexes WHERE indexname = %s", (index_name,) + # ) + # if not self.env.cr.fetchone(): + # self.env.cr.execute( + # sql.SQL( + # """ + # CREATE INDEX {index_name} ON {table} + # ({sequence_index}, + # l10n_do_sequence_prefix desc, + # l10n_do_sequence_number desc, + # {field}); + # CREATE INDEX {index2_name} ON {table} + # ({sequence_index}, + # id desc, + # l10n_do_sequence_prefix); + # """ + # ).format( + # sequence_index=sql.Identifier(self._sequence_index), + # index_name=sql.Identifier(index_name), + # index2_name=sql.Identifier(index_name + "2"), + # table=sql.Identifier(self._table), + # field=sql.Identifier(self._l10n_do_sequence_field), + # ) + # ) + + def _auto_init(self): + if not index_exists(self.env.cr, "account_move_unique_l10n_do_fiscal_number_sales"): + drop_index(self.env.cr, "unique_l10n_do_fiscal_number_purchase_manual", self._table) + drop_index(self.env.cr, "unique_l10n_do_fiscal_number_purchase_internal", self._table) self.env.cr.execute( - "SELECT indexname FROM pg_indexes WHERE indexname = %s", (index_name,) + """ + CREATE UNIQUE INDEX account_move_unique_l10n_do_fiscal_number_sales + ON account_move(l10n_do_fiscal_number, company_id) + WHERE (state = 'posted' + AND (l10n_latam_document_type_id IS NOT NULL + AND move_type NOT IN ('in_invoice', 'in_refund', 'in_receipt'))); + CREATE UNIQUE INDEX unique_l10n_do_fiscal_number_purchase_manual + ON account_move(l10n_do_fiscal_number, commercial_partner_id, company_id) + WHERE (state = 'posted' + AND (l10n_latam_document_type_id IS NOT NULL AND move_type IN ('in_invoice', 'in_refund', 'in_receipt') + AND l10n_latam_manual_document_number = 't')); + CREATE UNIQUE INDEX unique_l10n_do_fiscal_number_purchase_internal + ON account_move(l10n_do_fiscal_number, company_id) + WHERE (state = 'posted' + AND (l10n_latam_document_type_id IS NOT NULL AND move_type IN ('in_invoice', 'in_refund', 'in_receipt') + AND l10n_latam_manual_document_number = 'f')); + """ ) - if not self.env.cr.fetchone(): - self.env.cr.execute( - sql.SQL( - """ - CREATE INDEX {index_name} ON {table} - ({sequence_index}, - l10n_do_sequence_prefix desc, - l10n_do_sequence_number desc, - {field}); - CREATE INDEX {index2_name} ON {table} - ({sequence_index}, - id desc, - l10n_do_sequence_prefix); - """ - ).format( - sequence_index=sql.Identifier(self._sequence_index), - index_name=sql.Identifier(index_name), - index2_name=sql.Identifier(index_name + "2"), - table=sql.Identifier(self._table), - field=sql.Identifier(self._l10n_do_sequence_field), - ) - ) + return super()._auto_init() @api.model def _name_search( @@ -204,7 +247,9 @@ def _compute_l10n_do_show_expiration_date_msg(self): and not inv.l10n_latam_manual_document_number ) for invoice in l10n_do_internal_invoices: - invoice.l10n_do_show_expiration_date_msg = invoice._l10n_do_is_new_expiration_date() + invoice.l10n_do_show_expiration_date_msg = ( + invoice._l10n_do_is_new_expiration_date() + ) (self - l10n_do_internal_invoices).l10n_do_show_expiration_date_msg = False @@ -226,21 +271,24 @@ def _compute_l10n_do_enable_first_sequence(self): and not inv.l10n_latam_manual_document_number ) for invoice in l10n_do_internal_invoices: - invoice.l10n_do_enable_first_sequence = not bool( - self.search_count( - [ - ("company_id", "=", invoice.company_id.id), - ("move_type", "=", invoice.move_type), - ( - "l10n_latam_document_type_id", - "=", - invoice.l10n_latam_document_type_id.id, - ), - ("posted_before", "=", True), - ("id", "!=", invoice.id or invoice._origin.id), - ], + invoice.l10n_do_enable_first_sequence = ( + not bool( + self.search_count( + [ + ("company_id", "=", invoice.company_id.id), + ("move_type", "=", invoice.move_type), + ( + "l10n_latam_document_type_id", + "=", + invoice.l10n_latam_document_type_id.id, + ), + ("posted_before", "=", True), + ("id", "!=", invoice.id or invoice._origin.id), + ], + ) ) - ) or invoice.l10n_do_show_expiration_date_msg + or invoice.l10n_do_show_expiration_date_msg + ) (self - l10n_do_internal_invoices).l10n_do_enable_first_sequence = False @@ -350,39 +398,39 @@ def _compute_l10n_do_electronic_stamp(self): (self - l10n_do_ecf_invoice).l10n_do_electronic_stamp = False - @api.constrains("name", "journal_id", "state", "l10n_do_fiscal_number") - def _check_unique_sequence_number(self): - l10n_do_invoices = self.filtered( - lambda inv: inv.l10n_latam_use_documents - and inv.country_code == "DO" - and inv.is_sale_document() - and inv.state == "posted" - ) - if l10n_do_invoices: - self.flush_model( - ["name", "journal_id", "move_type", "state", "l10n_do_fiscal_number"] - ) - self._cr.execute( - """ - SELECT move2.id, move2.l10n_do_fiscal_number - FROM account_move move - INNER JOIN account_move move2 ON - move2.l10n_do_fiscal_number = move.l10n_do_fiscal_number - AND move2.journal_id = move.journal_id - AND move2.move_type = move.move_type - AND move2.id != move.id - WHERE move.id IN %s AND move2.state = 'posted' - """, - [tuple(l10n_do_invoices.ids)], - ) - res = self._cr.fetchone() - if res: - raise ValidationError( - _("There is already a sale invoice with fiscal number %s") - % self.l10n_do_fiscal_number - ) - - super(AccountMove, (self - l10n_do_invoices))._check_unique_sequence_number() + # @api.constrains("name", "journal_id", "state", "l10n_do_fiscal_number") + # def _check_unique_sequence_number(self): + # l10n_do_invoices = self.filtered( + # lambda inv: inv.l10n_latam_use_documents + # and inv.country_code == "DO" + # and inv.is_sale_document() + # and inv.state == "posted" + # ) + # if l10n_do_invoices: + # self.flush_model( + # ["name", "journal_id", "move_type", "state", "l10n_do_fiscal_number"] + # ) + # self._cr.execute( + # """ + # SELECT move2.id, move2.l10n_do_fiscal_number + # FROM account_move move + # INNER JOIN account_move move2 ON + # move2.l10n_do_fiscal_number = move.l10n_do_fiscal_number + # AND move2.journal_id = move.journal_id + # AND move2.move_type = move.move_type + # AND move2.id != move.id + # WHERE move.id IN %s AND move2.state = 'posted' + # """, + # [tuple(l10n_do_invoices.ids)], + # ) + # res = self._cr.fetchone() + # if res: + # raise ValidationError( + # _("There is already a sale invoice with fiscal number %s") + # % self.l10n_do_fiscal_number + # ) + # + # super(AccountMove, (self - l10n_do_invoices))._check_unique_sequence_number() @api.constrains( "l10n_do_fiscal_number", "partner_id", "company_id", "posted_before" diff --git a/l10n_do_accounting/models/account_move_line.py b/l10n_do_accounting/models/account_move_line.py index a297cd664..84fa173ab 100644 --- a/l10n_do_accounting/models/account_move_line.py +++ b/l10n_do_accounting/models/account_move_line.py @@ -14,7 +14,7 @@ class AccountMoveLine(models.Model): @api.depends("quantity", "discount", "price_unit", "tax_ids", "currency_id") def _compute_totals(self): - super()._compute_totals() + super(AccountMoveLine, self)._compute_totals() for line in self: if line.display_type != "product": line.l10n_do_itbis_amount = False diff --git a/l10n_do_accounting/models/monkey_patch.py b/l10n_do_accounting/models/monkey_patch.py index 6038d33b1..057787432 100644 --- a/l10n_do_accounting/models/monkey_patch.py +++ b/l10n_do_accounting/models/monkey_patch.py @@ -1,96 +1,15 @@ -from collections import defaultdict from odoo import models, api -from odoo.exceptions import ValidationError class AccountMove(models.Model): _inherit = "account.move" - @api.depends("posted_before", "state", "journal_id", "date") + @api.depends( + "posted_before", "state", "journal_id", "date", "move_type", "payment_id" + ) def _compute_name(self): - def journal_key(move): - return (move.journal_id, move.journal_id.refund_sequence and move.move_type) - def date_key(move): - return (move.date.year, move.date.month) - - grouped = defaultdict( # key: journal_id, move_type - lambda: defaultdict( # key: first adjacent (date.year, date.month) - lambda: { - "records": self.env["account.move"], - "format": False, - "format_values": False, - "reset": False, - } - ) - ) - self = self.sorted(lambda m: (m.date, m.ref or "", m.id)) - highest_name = self[0]._get_last_sequence(lock=False) if self else False - - # Group the moves by journal and month - for move in self: - if ( - not highest_name - and move == self[0] - and not move.posted_before - and move.date - ): - # In the form view, we need to compute a default sequence so that the user can edit - # it. We only check the first move as an approximation (enough for new in form view) - pass - elif (move.name and move.name != "/") or move.state != "posted": - try: - if not move.posted_before: - move._constrains_date_sequence() - # Has already a name or is not posted, we don't add to a batch - continue - except ValidationError: - # Has never been posted and the name doesn't match the date: recompute it - pass - group = grouped[journal_key(move)][date_key(move)] - if not group["records"]: - # Compute all the values needed to sequence this whole group - move._set_next_sequence() - ( - group["format"], - group["format_values"], - ) = move._get_sequence_format_param(move.name) - group["reset"] = move._deduce_sequence_number_reset(move.name) - group["records"] += move - - # Fusion the groups depending on the sequence reset and the format used because `seq` is - # the same counter for multiple groups that might be spread in multiple months. - final_batches = [] - for journal_group in grouped.values(): - journal_group_changed = True - for date_group in journal_group.values(): - if ( - journal_group_changed - or final_batches[-1]["format"] != date_group["format"] - or dict(final_batches[-1]["format_values"], seq=0) - != dict(date_group["format_values"], seq=0) - ): - final_batches += [date_group] - journal_group_changed = False - elif date_group["reset"] == "never": - final_batches[-1]["records"] += date_group["records"] - elif ( - date_group["reset"] == "year" - and final_batches[-1]["records"][0].date.year - == date_group["records"][0].date.year - ): - final_batches[-1]["records"] += date_group["records"] - else: - final_batches += [date_group] - - # Give the name based on previously computed values - for batch in final_batches: - for move in batch["records"]: - move.name = batch["format"].format(**batch["format_values"]) - batch["format_values"]["seq"] += 1 - batch["records"]._compute_split_sequence() - - self.filtered(lambda m: not m.name).name = "/" + super(AccountMove, self)._compute_name() for move in self.filtered( lambda x: x.country_code == "DO" diff --git a/l10n_do_accounting/views/res_config_settings_view.xml b/l10n_do_accounting/views/res_config_settings_view.xml index 77b5d6535..f27605bf2 100644 --- a/l10n_do_accounting/views/res_config_settings_view.xml +++ b/l10n_do_accounting/views/res_config_settings_view.xml @@ -7,10 +7,10 @@ -
+

Dominican Localization

-