Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14.0][IMP]l10n_it_delivery_note: split move lines based on dn #4375

Draft
wants to merge 1 commit into
base: 14.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 37 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,20 @@ 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
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 +190,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")
120 changes: 87 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,116 @@

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

delivery_note_ids = self.env.context.get("delivery_note_ids")
self._cancel_delivery_note_lines()

all_invoice_lines = invoice_ids.invoice_line_ids
for sol in self.order_line:
if not (sol.qty_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

if dn_lines and delivery_note_ids:
dn_lines = dn_lines.filtered(
lambda l: l.delivery_note_id in delivery_note_ids
)

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 122 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#L122

Added line #L122 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
8 changes: 5 additions & 3 deletions l10n_it_delivery_note/models/stock_delivery_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,9 +754,11 @@ def action_invoice(self, invoice_method=False):
if order_lines.filtered(lambda l: l.need_to_be_invoiced):
cache[downpayment] = downpayment.fix_qty_to_invoice()

invoice_ids = sale_ids.filtered(
lambda o: o.invoice_status == DOMAIN_INVOICE_STATUSES[1]
)._create_invoices(final=True)
invoice_ids = (
sale_ids.with_context(delivery_note_ids=self)
.filtered(lambda o: o.invoice_status == DOMAIN_INVOICE_STATUSES[1])
._create_invoices(final=True)
)

for line, vals in cache.items():
line.write(vals)
Expand Down
Loading
Loading