diff --git a/l10n_it_delivery_note/models/account_invoice.py b/l10n_it_delivery_note/models/account_invoice.py index 0a2b299a46ba..91e12cdbc676 100644 --- a/l10n_it_delivery_note/models/account_invoice.py +++ b/l10n_it_delivery_note/models/account_invoice.py @@ -75,6 +75,11 @@ def goto_invoice(self, **kwargs): } def _prepare_note_dn_value(self, sequence, delivery_note_id): + delivery_note_line_sequence = self.invoice_line_ids.filtered( + lambda x: x.delivery_note_id == delivery_note_id + ).mapped("sequence") + if delivery_note_line_sequence: + sequence = min(delivery_note_line_sequence) - 1 return { "sequence": sequence, "display_type": "line_note", @@ -124,23 +129,43 @@ def update_delivery_note_lines(self): ) ) else: - for line in invoice.invoice_line_ids: - sequence = line.sequence - 1 - delivery_note_line = invoice.mapped( - "delivery_note_ids.line_ids" - ) & line.mapped("sale_line_ids.delivery_note_line_ids") - for delivery_note_id in delivery_note_line.filtered( - lambda l: l.invoice_status # noqa: E741 - == DOMAIN_INVOICE_STATUSES[2] - ).mapped("delivery_note_id"): - line.delivery_note_id = delivery_note_id.id - new_lines.append( - ( - 0, - False, - self._prepare_note_dn_value(sequence, delivery_note_id), - ) + sequence = 1 + done_invoice_lines = self.env["account.move.line"] + delivery_notes = invoice.mapped( + "invoice_line_ids.sale_line_ids.delivery_note_line_ids." + "delivery_note_id" + ).sorted(key="name") + for dn in delivery_notes: + dn_invoice_lines = invoice.invoice_line_ids.filtered( + lambda x, dn=dn, done_invoice_lines=done_invoice_lines: x + not in done_invoice_lines + and dn + in x.mapped( + "sale_line_ids.delivery_note_line_ids.delivery_note_id" ) + # fixme test invoice from 2 sale lines + ) + done_invoice_lines |= dn_invoice_lines + for note_line in dn.line_ids.filtered( + lambda line: line.invoice_status == DOMAIN_INVOICE_STATUSES[2] + ): + for invoice_line in dn_invoice_lines.filtered( + lambda x: not x.delivery_note_id + ): + if ( + note_line + in invoice_line.sale_line_ids.delivery_note_line_ids + ): + invoice_line.delivery_note_id = ( + note_line.delivery_note_id.id + ) + new_lines.append( + ( + 0, + False, + self._prepare_note_dn_value(sequence, dn), + ) + ) invoice.write({"line_ids": new_lines}) diff --git a/l10n_it_delivery_note/models/res_config_settings.py b/l10n_it_delivery_note/models/res_config_settings.py index 7e953ba0d368..bf4d24915f32 100644 --- a/l10n_it_delivery_note/models/res_config_settings.py +++ b/l10n_it_delivery_note/models/res_config_settings.py @@ -49,3 +49,6 @@ def _default_virtual_locations_root(self): related="company_id.display_delivery_method_dn_report", readonly=False, ) + delivery_note_group_by_quantity = fields.Boolean( + string="Group Delivery note invoices by quantity" + ) diff --git a/l10n_it_delivery_note/models/sale_order.py b/l10n_it_delivery_note/models/sale_order.py index 6e38f6d95423..981abc7366b3 100644 --- a/l10n_it_delivery_note/models/sale_order.py +++ b/l10n_it_delivery_note/models/sale_order.py @@ -1,7 +1,10 @@ # Copyright (c) 2019, Link IT Europe Srl # @author: Matteo Bilotta +from itertools import groupby from odoo import api, fields, models +from odoo.exceptions import AccessError, UserError +from odoo.fields import Command from .stock_delivery_note import DOMAIN_DELIVERY_NOTE_STATES, DOMAIN_INVOICE_STATUSES @@ -121,13 +124,14 @@ def _generate_delivery_note_lines(self, invoice_ids): invoices = self.env["account.move"].browse(invoice_ids) invoices.update_delivery_note_lines() - def _create_invoices(self, grouped=False, final=False, date=None): - invoice_ids = super()._create_invoices(grouped=grouped, final=final, date=date) + # def _create_invoices(self, grouped=False, final=False, date=None): + # invoice_ids = super()._create_invoices(grouped=grouped, + # final=final, date=date) - self._assign_delivery_notes_invoices(invoice_ids.ids) - self._generate_delivery_note_lines(invoice_ids.ids) + # self._assign_delivery_notes_invoices(invoice_ids.ids) + # self._generate_delivery_note_lines(invoice_ids.ids) - return invoice_ids + # return invoice_ids def goto_delivery_notes(self, **kwargs): delivery_notes = self.mapped("delivery_note_ids") @@ -154,3 +158,138 @@ def goto_delivery_notes(self, **kwargs): action = {"type": "ir.actions.act_window_close"} return action + + def _create_invoices(self, grouped=False, final=False, date=None): # noqa: C901 + if ( + not self.env["ir.config_parameter"] + .sudo() + .get_param("l10n_it_delivery_note.delivery_note_group_by_quantity") + ): + invoice_ids = super()._create_invoices(grouped, final, date) + self._assign_delivery_notes_invoices(invoice_ids.ids) + self._generate_delivery_note_lines(invoice_ids.ids) + return invoice_ids + if not self.env["account.move"].check_access_rights("create", False): + try: + self.check_access_rights("write") + self.check_access_rule("write") + except AccessError: + return self.env["account.move"] + + # 1) Create invoices. + invoice_vals_list = [] + invoice_item_sequence = ( + 10 # Incremental sequencing to keep the lines order on the invoice. + ) + for order in self: + order = order.with_company(order.company_id).with_context( + lang=order.partner_invoice_id.lang + ) + + invoice_vals = order._prepare_invoice() + invoiceable_lines = order._get_invoiceable_lines(final) + + # if not any(not line.display_type for line in invoiceable_lines): + # continue + + invoice_line_vals = [] + down_payment_section_added = False + for line in invoiceable_lines.mapped("delivery_note_line_ids"): + if not down_payment_section_added and line.sale_line_id.is_downpayment: + # Create a dedicated section for the down payments + # (put at the end of the invoiceable_lines) + invoice_line_vals.append( + Command.create( + order._prepare_down_payment_section_line( + sequence=invoice_item_sequence + ) + ), + ) + down_payment_section_added = True + invoice_item_sequence += 10 + invoice_line_vals.append( + Command.create( + line._prepare_ddt_invoice_line(sequence=invoice_item_sequence) + ), + ) + invoice_item_sequence += 10 + + invoice_vals["invoice_line_ids"] += invoice_line_vals + invoice_vals_list.append(invoice_vals) + + if not invoice_vals_list and self._context.get( + "raise_if_nothing_to_invoice", True + ): + raise UserError(self._nothing_to_invoice_error_message()) + + # 2) Manage 'grouped' parameter: group by (partner_id, currency_id). + if not grouped: + new_invoice_vals_list = [] + invoice_grouping_keys = self._get_invoice_grouping_keys() + invoice_vals_list = sorted( + invoice_vals_list, + key=lambda x: [ + x.get(grouping_key) for grouping_key in invoice_grouping_keys + ], + ) + for _grouping_keys, invoices in groupby( + invoice_vals_list, + key=lambda x: [ + x.get(grouping_key) for grouping_key in invoice_grouping_keys + ], + ): + origins = set() + payment_refs = set() + refs = set() + ref_invoice_vals = None + for invoice_vals in invoices: + if not ref_invoice_vals: + ref_invoice_vals = invoice_vals + else: + ref_invoice_vals["invoice_line_ids"] += invoice_vals[ + "invoice_line_ids" + ] + origins.add(invoice_vals["invoice_origin"]) + payment_refs.add(invoice_vals["payment_reference"]) + refs.add(invoice_vals["ref"]) + ref_invoice_vals.update( + { + "ref": ", ".join(refs)[:2000], + "invoice_origin": ", ".join(origins), + "payment_reference": len(payment_refs) == 1 + and payment_refs.pop() + or False, + } + ) + new_invoice_vals_list.append(ref_invoice_vals) + invoice_vals_list = new_invoice_vals_list + if len(invoice_vals_list) < len(self): + SaleOrderLine = self.env["sale.order.line"] + for invoice in invoice_vals_list: + sequence = 1 + for line in invoice["invoice_line_ids"]: + line[2]["sequence"] = SaleOrderLine._get_invoice_line_sequence( + new=sequence, old=line[2]["sequence"] + ) + sequence += 1 + + moves = ( + self.env["account.move"] + .sudo() + .with_context(default_move_type="out_invoice") + .create(invoice_vals_list) + ) + if final: + moves.sudo().filtered( + lambda m: m.amount_total < 0 + ).action_switch_invoice_into_refund_credit_note() + for move in moves: + move.message_post_with_view( + "mail.message_origin_link", + values={"self": move, "origin": move.line_ids.sale_line_ids.order_id}, + subtype_id=self.env["ir.model.data"]._xmlid_to_res_id("mail.mt_note"), + ) + # return moves + self._assign_delivery_notes_invoices(moves.ids) + self._generate_delivery_note_lines(moves.ids) + return moves diff --git a/l10n_it_delivery_note/models/stock_delivery_note.py b/l10n_it_delivery_note/models/stock_delivery_note.py index 25f256b4e8d4..3451ef44a96c 100644 --- a/l10n_it_delivery_note/models/stock_delivery_note.py +++ b/l10n_it_delivery_note/models/stock_delivery_note.py @@ -304,11 +304,9 @@ def _domain_weight_uom(self): def _check_transport_reason_inconsistency(self): for note in self: if ( - note.type_id - and note.type_id.transport_reason_inconsistency_ids + note.transport_reason_id and note.transport_reason_id - and note.transport_reason_id.id - in note.type_id.transport_reason_inconsistency_ids.ids + in note.type_id.transport_reason_inconsistency_ids ): raise UserError( _( @@ -673,19 +671,17 @@ def _check_delivery_notes_before_invoicing(self): if line.product_id.invoice_policy == "order": raise UserError( _( - "In %(ddt_name)s there is %(product_name)s" - " with invoicing policy 'order'" + "In %(dn_name)s there is %(product_name)s " + "with invoicing policy 'order'", + dn_name=delivery_note_id.display_name, + product_name=line.product_id.name, ) - % { - "ddt_name": delivery_note_id.display_name, - "product_name": line.product_id.name, - } ) def _fix_quantities_to_invoice(self, lines, invoice_method): cache = {} - pickings_lines = lines.retrieve_pickings_lines(self.picking_ids) + pickings_lines = lines.retrieve_pickings_lines(self.mapped("picking_ids")) other_lines = lines - pickings_lines if not invoice_method or invoice_method == "dn": @@ -716,16 +712,17 @@ def action_invoice(self, invoice_method=False): ] for payment_term_id in payment_term_ids: sale_ids = self.mapped("sale_ids").filtered( - lambda s, pay_term_id=payment_term_id: s.payment_term_id == pay_term_id + lambda s, payment_term_id=payment_term_id: s.payment_term_id + == payment_term_id ) if not sale_ids: continue orders_lines = sale_ids.mapped("order_line").filtered( - lambda l: l.product_id # noqa: E741 + lambda line: line.product_id ) - downpayment_lines = orders_lines.filtered(lambda l: l.is_downpayment) # noqa: E741 - invoiceable_lines = orders_lines.filtered(lambda l: l.is_invoiceable) # noqa: E741 + downpayment_lines = orders_lines.filtered(lambda line: line.is_downpayment) + invoiceable_lines = orders_lines.filtered(lambda line: line.is_invoiceable) cache = self._fix_quantities_to_invoice( invoiceable_lines - downpayment_lines, invoice_method @@ -734,13 +731,13 @@ def action_invoice(self, invoice_method=False): for downpayment in downpayment_lines: order = downpayment.order_id order_lines = order.order_line.filtered( - lambda l: l.product_id and not l.is_downpayment # noqa: E741 + lambda line: line.product_id and not line.is_downpayment ) - if order_lines.filtered(lambda l: l.need_to_be_invoiced): # noqa: E741 + if order_lines.filtered(lambda line: line.need_to_be_invoiced): cache[downpayment] = downpayment.fix_qty_to_invoice() - invoice_ids = sale_ids.filtered( + invoice_ids = self.sale_ids.filtered( lambda o: o.invoice_status == DOMAIN_INVOICE_STATUSES[1] )._create_invoices(final=True) @@ -768,6 +765,27 @@ def action_invoice(self, invoice_method=False): invoices = self.env["account.move"].browse(invoice_ids.ids) invoices.update_delivery_note_lines() + if all( + line.invoice_status == "invoiced" for line in self.mapped("line_ids") + ): + for delivery_note in self: + ready_invoice_ids = [ + invoice_id + for invoice_id in delivery_note.sale_ids.mapped( + "invoice_ids" + ).ids + if invoice_id in invoices.ids + ] + delivery_note.write( + { + "invoice_ids": [ + (4, invoice_id) for invoice_id in ready_invoice_ids + ] + } + ) + self._compute_invoice_status() + invoices.update_delivery_note_lines() + def action_done(self): self.write({"state": DOMAIN_DELIVERY_NOTE_STATES[3]}) diff --git a/l10n_it_delivery_note/models/stock_delivery_note_line.py b/l10n_it_delivery_note/models/stock_delivery_note_line.py index 6809ab167346..0905c0b64288 100644 --- a/l10n_it_delivery_note/models/stock_delivery_note_line.py +++ b/l10n_it_delivery_note/models/stock_delivery_note_line.py @@ -5,6 +5,7 @@ from odoo import _, api, fields, models from odoo.exceptions import UserError +from odoo.fields import Command DATE_FORMAT = "%d/%m/%Y" DATETIME_FORMAT = "%d/%m/%Y %H:%M:%S" @@ -197,3 +198,27 @@ def sync_invoice_status(self): if invoice_status == "upselling" else invoice_status ) + + def _prepare_ddt_invoice_line(self, **optional_values): + self.ensure_one() + sequence = optional_values["sequence"] or self.sequence + res = { + "display_type": "product", + "sequence": sequence, + "name": self.name, + "product_id": self.product_id.id, + "product_uom_id": self.product_uom_id.id, + "quantity": self.product_qty, + "price_unit": self.price_unit, + "tax_ids": [Command.set(self.tax_ids.ids)], + "delivery_note_id": self.delivery_note_id.id, + } + if self.sale_line_id: + res.update( + { + "sale_line_ids": [Command.link(self.sale_line_id.id)], + "is_downpayment": self.sale_line_id.is_downpayment, + "discount": self.sale_line_id.discount, + } + ) + return res diff --git a/l10n_it_delivery_note/views/res_config_settings.xml b/l10n_it_delivery_note/views/res_config_settings.xml index ddbacee53f59..93a64ebda720 100644 --- a/l10n_it_delivery_note/views/res_config_settings.xml +++ b/l10n_it_delivery_note/views/res_config_settings.xml @@ -121,6 +121,22 @@ +

Group Delivery Notes by quantity

+
+
+
+ +
+
+
+
+
diff --git a/l10n_it_delivery_note/wizard/delivery_note_invoice.py b/l10n_it_delivery_note/wizard/delivery_note_invoice.py index 21b530e89766..efa9fd834e26 100644 --- a/l10n_it_delivery_note/wizard/delivery_note_invoice.py +++ b/l10n_it_delivery_note/wizard/delivery_note_invoice.py @@ -3,7 +3,7 @@ # @author: Giuseppe Borruso # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import _, api, fields, models INVOICE_STATUSES = [ ("no", "Nothing to invoice"), @@ -35,6 +35,19 @@ def create_invoices(self): self._context.get("active_ids", []) ) delivery_note_ids.action_invoice(self.invoice_method) - for invoice in delivery_note_ids.mapped("invoice_ids"): - invoice.invoice_date = self.invoice_date - return True + invoice_ids = delivery_note_ids.mapped("invoice_ids") + if invoice_ids: + for invoice in invoice_ids: + invoice.invoice_date = self.invoice_date + form_id = self.env.ref("account.view_move_form").id + tree_id = self.env.ref("account.view_move_tree").id + return { + "name": _("Invoices from TD"), + "view_mode": "form,tree", + "res_model": "account.move", + "domain": [("id", "in", invoice_ids.ids)], + "view_id": False, + "views": [(tree_id, "tree"), (form_id, "form")], + "context": "{'move_type': 'out_invoice'}", + "type": "ir.actions.act_window", + }