Skip to content

Commit

Permalink
[IMP]l10n_it_delivery_note: split move lines based on dn
Browse files Browse the repository at this point in the history
  • Loading branch information
aleuffre authored and PicchiSeba committed Aug 27, 2024
1 parent 9544c78 commit c97d8b5
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 73 deletions.
74 changes: 38 additions & 36 deletions l10n_it_delivery_note/models/account_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
# @author: Matteo Bilotta <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from collections import defaultdict

from odoo import _, fields, models

from .stock_delivery_note import DATE_FORMAT, DOMAIN_INVOICE_STATUSES
from .stock_delivery_note import DATE_FORMAT


class AccountInvoice(models.Model):
Expand Down Expand Up @@ -87,13 +89,27 @@ def _prepare_note_dn_value(self, sequence, delivery_note_id):
"quantity": 0,
}

def _has_dn_line_note(self, delivery_note):
self.ensure_one()
return bool(
self.invoice_line_ids.filtered(
lambda line, dn=delivery_note: line.display_type == "line_note"
and line.delivery_note_id == dn
)
)

def update_delivery_note_lines(self):
context = {}

for invoice in self.filtered(lambda i: i.delivery_note_ids):
sequence = 1
new_lines = []
old_lines = invoice.invoice_line_ids.filtered(lambda l: l.note_dn)
old_lines.unlink()

# Build a dictionary {delivery.note(1, 2): account.move.line(3, 5)}
inv_line_by_dn = defaultdict(self.env["account.move.line"].browse)
for inv_line in invoice.invoice_line_ids:
dn = inv_line.delivery_note_line_id.delivery_note_id
inv_line_by_dn[dn] |= inv_line

#
# TODO: Come bisogna comportarsi nel caso in
Expand All @@ -111,7 +127,9 @@ def update_delivery_note_lines(self):
#
context["lang"] = invoice.partner_id.lang

if len(invoice.delivery_note_ids) == 1:
if len(invoice.delivery_note_ids) == 1 and not invoice._has_dn_line_note(
invoice.delivery_note_ids[0]
):
sequence = invoice.invoice_line_ids[0].sequence - 1
new_lines.append(
(
Expand All @@ -123,40 +141,21 @@ def update_delivery_note_lines(self):
)
)
else:
sequence = 1
done_invoice_lines = self.env["account.move.line"]
for dn in invoice.mapped("delivery_note_ids").sorted(key="name"):
dn_invoice_lines = invoice.invoice_line_ids.filtered(
lambda x: x not in done_invoice_lines
and dn
in x.mapped(
"sale_line_ids.delivery_note_line_ids.delivery_note_id"
for dn in inv_line_by_dn:
if not dn or invoice._has_dn_line_note(dn):
continue
# import wdb; wdb.set_trace()
new_lines_vals = self._prepare_note_dn_value(sequence, dn)
new_lines.append(
(
0,
False,
new_lines_vals,
)
# fixme test invoice from 2 sale lines
)
done_invoice_lines |= dn_invoice_lines
for note_line in dn.line_ids.filtered(
lambda l: l.invoice_status == DOMAIN_INVOICE_STATUSES[2]
):
for invoice_line in dn_invoice_lines:
if (
note_line
in invoice_line.sale_line_ids.delivery_note_line_ids
):
invoice_line.delivery_note_id = (
note_line.delivery_note_id.id
)
if dn_invoice_lines:
new_lines.append(
(
0,
False,
self._prepare_note_dn_value(sequence, dn),
)
)
sequence += 1
for invoice_line in dn_invoice_lines:
invoice_line.sequence = sequence
sequence += 1
for line in inv_line_by_dn[dn]:
line.sequence = sequence
sequence += 1

invoice.write({"line_ids": new_lines})
Expand Down Expand Up @@ -192,4 +191,7 @@ class AccountInvoiceLine(models.Model):
delivery_note_id = fields.Many2one(
"stock.delivery.note", string="Delivery Note", readonly=True, copy=False
)
delivery_note_line_id = fields.Many2one(
"stock.delivery.note.line", string="Delivery Note", readonly=True, copy=False
)
note_dn = fields.Boolean(string="Note DN")
113 changes: 80 additions & 33 deletions l10n_it_delivery_note/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright (c) 2019, Link IT Europe Srl
# @author: Matteo Bilotta <[email protected]>

from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import UserError

from .stock_delivery_note import DOMAIN_DELIVERY_NOTE_STATES, DOMAIN_INVOICE_STATUSES

Expand Down Expand Up @@ -52,63 +53,109 @@ def onchange_partner_id_shipping_info(self):

self.update(values)

def _assign_delivery_notes_invoices(self, invoice_ids):
def _cancel_delivery_note_lines(self):
order_lines = self.mapped("order_line").filtered(
lambda l: l.is_invoiced and l.delivery_note_line_ids
)

delivery_note_lines = order_lines.mapped("delivery_note_line_ids").filtered(
lambda l: l.is_invoiceable
)
delivery_notes = delivery_note_lines.mapped("delivery_note_id")

ready_delivery_notes = delivery_notes.filtered(
lambda n: n.state != DOMAIN_DELIVERY_NOTE_STATES[0]
)

draft_delivery_notes = delivery_notes - ready_delivery_notes
draft_delivery_note_lines = (
draft_delivery_notes.mapped("line_ids") & delivery_note_lines
)

ready_delivery_note_lines = delivery_note_lines - draft_delivery_note_lines

#
# TODO: È necessario gestire il caso di fatturazione splittata
# di una stessa riga d'ordine associata ad una sola
# picking (e di conseguenza, ad un solo DdT)?
# Può essere, invece, un caso "borderline"
# da lasciar gestire all'operatore?
# Personalmente, non lo gestirei e delegherei
# all'operatore questa responsabilità...
#

draft_delivery_note_lines.write(
{"invoice_status": DOMAIN_INVOICE_STATUSES[0], "sale_line_id": None}
)

ready_delivery_note_lines.write({"invoice_status": DOMAIN_INVOICE_STATUSES[2]})
for ready_delivery_note in ready_delivery_notes:
ready_invoice_ids = [
invoice_id
for invoice_id in ready_delivery_note.sale_ids.mapped("invoice_ids").ids
if invoice_id in invoice_ids
]
ready_delivery_note.write(
{"invoice_ids": [(4, invoice_id) for invoice_id in ready_invoice_ids]}
def _assign_delivery_notes_invoices(self, invoice_ids):
if not invoice_ids:
return

self._cancel_delivery_note_lines()

all_invoice_lines = invoice_ids.invoice_line_ids
for sol in self.order_line:
if not (sol.is_invoiced and sol.delivery_note_line_ids):
continue
dn_lines = sol.delivery_note_line_ids.filtered(
lambda l: l.is_invoiceable
and l.delivery_note_id.state
not in (
DOMAIN_DELIVERY_NOTE_STATES[0], # draft
DOMAIN_DELIVERY_NOTE_STATES[-1], # cancel
)
)

ready_delivery_notes._compute_invoice_status()
if not dn_lines:
continue
inv_lines = all_invoice_lines.filtered(
lambda line, s=sol: s in line.sale_line_ids
).with_context(check_move_validity=False)
inv_line = inv_lines[0] # safety guard
inv_line.write(
{
"delivery_note_line_id": dn_lines[0],
"delivery_note_id": dn_lines[0].delivery_note_id,
}
)
if len(dn_lines) > 1:
inv_line.quantity = dn_lines[0].product_qty
move_id = inv_line.move_id
remaining_dn_lines = dn_lines[1:]
product = sol.product_id
for dn_line in remaining_dn_lines:
move_line = dn_line.move_id.move_line_ids.filtered(
lambda l, p=product: l.product_id == p
)
if len(move_line) != 1:
raise UserError(

Check warning on line 115 in l10n_it_delivery_note/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

l10n_it_delivery_note/models/sale_order.py#L115

Added line #L115 was not covered by tests
_(
"No unique matching move line was found for %(sol)s in"
" Stock Move %(move)s"
)
% {
"sol": sol.name,
"move": dn_line.move_id.name,
}
)
new_data = inv_line.copy_data(
{
"quantity": move_line.qty_done,
"price_unit": inv_line.price_unit,
"delivery_note_line_id": dn_line.id,
"delivery_note_id": dn_line.delivery_note_id.id,
"sale_line_ids": [(6, 0, inv_line.sale_line_ids.ids)],
}
)[0]
move_id.write({"invoice_line_ids": [(0, 0, new_data)]})
# We are setting `inv_line.quantity` again because
# `_move_autocomplete_invoice_lines_write()` applies the new changes on
# a temporary copy of the original invoice that fetches outdated data
# thus requiring a second write
inv_line.quantity = dn_lines[0].product_qty
move_id._onchange_invoice_line_ids()
dn_lines.write(
{
"invoice_status": DOMAIN_INVOICE_STATUSES[2],
}
)
for dn in dn_lines.mapped("delivery_note_id"):
dn.invoice_ids += inv_lines.mapped("move_id")
dn._compute_invoice_status()
invoice_ids._check_balanced()

def _generate_delivery_note_lines(self, invoice_ids):
invoices = self.env["account.move"].browse(invoice_ids)
invoices.update_delivery_note_lines()
invoice_ids.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)

self._assign_delivery_notes_invoices(invoice_ids.ids)
self._generate_delivery_note_lines(invoice_ids.ids)
self._assign_delivery_notes_invoices(invoice_ids)
self._generate_delivery_note_lines(invoice_ids)

return invoice_ids

Expand Down
24 changes: 20 additions & 4 deletions l10n_it_delivery_note/tests/test_stock_delivery_note_invoicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1356,10 +1356,12 @@ def test_invoicing_multiple_dn(self):
invoice.action_post()
self.assertEqual(invoice.state, "posted")
self.assertEqual(
invoice.invoice_line_ids.filtered(
lambda inv_line: inv_line.product_id.id
== self.right_corner_desk_line[2]["product_id"]
).quantity,
sum(
invoice.invoice_line_ids.filtered(
lambda inv_line: inv_line.product_id.id
== self.right_corner_desk_line[2]["product_id"]
).mapped("quantity")
),
2,
)
self.assertEqual(
Expand All @@ -1379,6 +1381,20 @@ def test_invoicing_multiple_dn(self):
f'Delivery Note "{back_dn.name}" of {back_dn.date.strftime(DATE_FORMAT)}',
invoice.invoice_line_ids.mapped("name"),
)
invoice_lines = invoice.invoice_line_ids.sorted("sequence")
for delivery_note in invoice_lines.mapped("delivery_note_id"):
inv_dn_lines = invoice_lines.filtered(
lambda l, dn=delivery_note: l.delivery_note_id == dn
)
note_line = inv_dn_lines.filtered(lambda l: l.note_dn)
self.assertEqual(len(note_line), 1)
# Check that the first line of a particular dn is a note
self.assertEqual(inv_dn_lines[0], note_line)
# Check that all lines of a particular dn are neighbouring each other
self.assertEqual(
len(inv_dn_lines),
int(inv_dn_lines[-1].sequence) - int(inv_dn_lines[0].sequence) + 1,
)

def test_invoicing_multi_dn_multi_so_same_product(self):
self.env["ir.config_parameter"].sudo().set_param(
Expand Down

0 comments on commit c97d8b5

Please sign in to comment.