Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
1
sale_stock_picking_invoicing/wizards/__init__.py
Normal file
1
sale_stock_picking_invoicing/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import stock_invoice_onshipping
|
||||
352
sale_stock_picking_invoicing/wizards/stock_invoice_onshipping.py
Normal file
352
sale_stock_picking_invoicing/wizards/stock_invoice_onshipping.py
Normal file
@@ -0,0 +1,352 @@
|
||||
# Copyright (C) 2020-TODAY KMEE
|
||||
# Copyright (C) 2021-TODAY Akretion
|
||||
# @author Magno Costa <magno.costa@akretion.com.br>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class StockInvoiceOnshipping(models.TransientModel):
|
||||
_inherit = "stock.invoice.onshipping"
|
||||
|
||||
@api.model
|
||||
def _default_has_down_payment(self):
|
||||
pickings = self._load_pickings()
|
||||
sale_pickings = pickings.filtered(lambda pk: pk.sale_id)
|
||||
downpayment_lines = False
|
||||
if sale_pickings:
|
||||
for pick in sale_pickings:
|
||||
# order = pick.sale_id
|
||||
# sale_lines = order.mapped("order_line")
|
||||
if pick.sale_id.order_line.filtered(lambda ln: ln.is_downpayment):
|
||||
downpayment_lines = True
|
||||
|
||||
return downpayment_lines
|
||||
|
||||
deduct_down_payments = fields.Boolean("Deduct down payments", default=True)
|
||||
has_down_payments = fields.Boolean(
|
||||
"Has down payments", default=_default_has_down_payment
|
||||
)
|
||||
|
||||
def _get_fields_not_used_from_sale(self):
|
||||
"""Fields not used from Sale 'prepare' method"""
|
||||
# For reference, fields get from 'prepare' method
|
||||
# "ref": self.client_order_ref or ''
|
||||
# "narration": self.note,
|
||||
# "campaign_id": self.campaign_id.id,
|
||||
# "medium_id": self.medium_id.id,
|
||||
# "source_id": self.source_id.id,
|
||||
# "team_id": self.team_id.id,
|
||||
# "partner_shipping_id": self.partner_shipping_id.id,
|
||||
# "partner_bank_id": self.company_id.partner_id.bank_ids.
|
||||
# filtered(lambda bank: bank.company_id.id in
|
||||
# (self.company_id.id, False))[:1].id,
|
||||
# "invoice_payment_term_id": self.payment_term_id.id,
|
||||
# "payment_reference": self.reference,
|
||||
# "transaction_ids": [(6, 0, self.transaction_ids.ids)],
|
||||
|
||||
return {
|
||||
"move_type",
|
||||
"currency_id",
|
||||
"user_id",
|
||||
"invoice_user_id",
|
||||
"partner_id",
|
||||
"fiscal_position_id",
|
||||
"journal_id", # company comes from the journal
|
||||
"invoice_origin",
|
||||
"invoice_line_ids",
|
||||
"company_id",
|
||||
# Another fields
|
||||
"__last_update",
|
||||
"display_name",
|
||||
}
|
||||
|
||||
def _build_invoice_values_from_pickings(self, pickings):
|
||||
invoice, values = super()._build_invoice_values_from_pickings(pickings)
|
||||
|
||||
sale_pickings = pickings.filtered(lambda pk: pk.sale_id)
|
||||
# Refund case don't get values from Sale Dict
|
||||
# TODO: Should get any value?
|
||||
if sale_pickings and self._get_invoice_type() != "out_refund":
|
||||
# Case more than one Sale Order the fields below will be join
|
||||
# the others will be overwritting, as done in sale module,
|
||||
# one more field include here Note/Narration
|
||||
payment_refs = set()
|
||||
refs = set()
|
||||
# Include Narration
|
||||
narration = set()
|
||||
for pick in sale_pickings.sorted(key=lambda p: p.name):
|
||||
# Other modules can included new fields in Sale Order and include
|
||||
# this fields in the dict of creation Invoice from sale, for
|
||||
# example:
|
||||
# - account_payment_sale
|
||||
# https://github.com/OCA/bank-payment/blob/14.0/
|
||||
# account_payment_sale/models/sale_order.py#L41
|
||||
# - sale_commssion
|
||||
# https://github.com/OCA/commission/blob/14.0/
|
||||
# sale_commission/models/sale_order.py#L64
|
||||
# To avoid the necessity of a 'glue' module the method get the
|
||||
# values from _prepare_invoice but removed some fields of the
|
||||
# original method, given priority for values from
|
||||
# stock_picking_invoicing dict, for now it's seems the best to
|
||||
# way to avoid the 'glue' modules problem.
|
||||
sale_values = pick.sale_id._prepare_invoice()
|
||||
# Fields to Join
|
||||
# origins.add(sale_values["invoice_origin"])
|
||||
payment_refs.add(sale_values["payment_reference"])
|
||||
refs.add(sale_values["ref"])
|
||||
narration.add(sale_values["narration"])
|
||||
|
||||
sale_values_rm = {
|
||||
k: sale_values[k]
|
||||
for k in set(sale_values) - self._get_fields_not_used_from_sale()
|
||||
}
|
||||
|
||||
values.update(sale_values_rm)
|
||||
|
||||
# Fields to join
|
||||
if len(sale_pickings) > 1:
|
||||
values.update(
|
||||
{
|
||||
"ref": ", ".join(refs)[:2000],
|
||||
# In this case Origin get Pickings Names
|
||||
# "invoice_origin": ", ".join(origins),
|
||||
"payment_reference": len(payment_refs) == 1
|
||||
and payment_refs.pop()
|
||||
or False,
|
||||
"narration": ", ".join(narration),
|
||||
}
|
||||
)
|
||||
|
||||
return invoice, values
|
||||
|
||||
# Check the comment below
|
||||
# def _get_picking_key(self, picking):
|
||||
# key = super()._get_picking_key(picking)
|
||||
# if picking.sale_id:
|
||||
# key = key + (
|
||||
# picking.sale_id.payment_term_id,
|
||||
# picking.sale_id.fiscal_position_id,
|
||||
# picking.sale_id.commitment_date,
|
||||
# picking.sale_id.analytic_account_id,
|
||||
# picking.sale_id.pricelist_id,
|
||||
# picking.sale_id.company_id,
|
||||
# )
|
||||
# return key
|
||||
|
||||
def _get_move_key(self, move):
|
||||
key = super()._get_move_key(move)
|
||||
if move.sale_line_id:
|
||||
# TODO: Analise if Sale Lines should be grouped.
|
||||
# For now remains a problem https://github.com/odoo/odoo/pull/77195
|
||||
# with field qty_invoiced at sale.order.line, when a invoice line
|
||||
# has more than one sale line related the field show the total QTY
|
||||
# of those lines, e.g:
|
||||
# product_uom_qty | qty_invoiced
|
||||
# 2.0 | 4.0
|
||||
# key = key + (
|
||||
# move.sale_line_id.price_unit,
|
||||
# move.sale_line_id.customer_lead,
|
||||
# move.sale_line_id.currency_id,
|
||||
# move.sale_line_id.tax_id,
|
||||
# move.sale_line_id.analytic_tag_ids,
|
||||
# )
|
||||
key = key + (move.sale_line_id,)
|
||||
|
||||
return key
|
||||
|
||||
def _get_fields_not_used_from_sale_line(self):
|
||||
"""Fields not used from Sale Line 'prepare' method"""
|
||||
# Original fields from sale module
|
||||
# Fields do get
|
||||
# "sequence": self.sequence,
|
||||
# "discount": self.discount,
|
||||
# "display_type": self.display_type or 'product'
|
||||
# "is_downpayment": self.is_downpayment
|
||||
# * optional_values
|
||||
# * 'N field' included in _prepare_invoice_line method
|
||||
# by another module
|
||||
|
||||
return {
|
||||
# Fields from Move has priority
|
||||
"name",
|
||||
"product_id",
|
||||
"product_uom_id",
|
||||
"quantity",
|
||||
"price_unit",
|
||||
"tax_ids",
|
||||
# Already get
|
||||
"sale_line_ids",
|
||||
"anlytic_distribution",
|
||||
# another fields
|
||||
"__last_update",
|
||||
"display_name",
|
||||
}
|
||||
|
||||
def _get_invoice_line_values(self, moves, invoice_values, invoice):
|
||||
values = super()._get_invoice_line_values(moves, invoice_values, invoice)
|
||||
move = fields.first(moves)
|
||||
if move.sale_line_id:
|
||||
# Same make above, get fields informed in Sale Line dict
|
||||
sale_line_values = move.sale_line_id._prepare_invoice_line()
|
||||
# Vals informed in any case
|
||||
values["sale_line_ids"] = [(6, 0, moves.sale_line_id.ids)]
|
||||
values["analytic_distribution"] = sale_line_values.get(
|
||||
"analytic_distribution"
|
||||
)
|
||||
# Refund case don't get values from Sale Line Dict
|
||||
# TODO: Should get any value?
|
||||
if self._get_invoice_type() != "out_refund":
|
||||
# Same make above, get fields informed in Sale Line dict
|
||||
sale_line_values = move.sale_line_id._prepare_invoice_line()
|
||||
|
||||
sale_line_values_rm = {
|
||||
k: sale_line_values[k]
|
||||
for k in set(sale_line_values)
|
||||
- self._get_fields_not_used_from_sale_line()
|
||||
}
|
||||
values.update(sale_line_values_rm)
|
||||
|
||||
return values
|
||||
|
||||
def _get_pickings_with_sale(self, invoice_values):
|
||||
pickings = self._load_pickings()
|
||||
|
||||
# Filter Picking with Sales
|
||||
picking_in_invoice_values = self.env["stock.picking"]
|
||||
for line in invoice_values.get("invoice_line_ids"):
|
||||
if line[2]:
|
||||
if len(line[2].get("move_line_ids")[0]) == 2:
|
||||
# [(4, 233)],
|
||||
move_line_id = line[2].get("move_line_ids")[0][1]
|
||||
else:
|
||||
# [(<Command.SET: 6>, 0, [51])]
|
||||
move_line_id = line[2].get("move_line_ids")[0][2]
|
||||
|
||||
move_line = self.env["stock.move"].browse(move_line_id)
|
||||
picking_in_invoice_values |= move_line.mapped("picking_id")
|
||||
|
||||
sale_pickings = pickings.filtered(
|
||||
lambda pk: pk.sale_id
|
||||
# Check Sales Ungrouped
|
||||
and pk.id in picking_in_invoice_values.ids
|
||||
)
|
||||
|
||||
return sale_pickings
|
||||
|
||||
def _create_invoice(self, invoice_values):
|
||||
"""Override this method if you need to change any values of the
|
||||
invoice and the lines before the invoice creation
|
||||
:param invoice_values: dict with the invoice and its lines
|
||||
:return: invoice
|
||||
"""
|
||||
sale_pickings = self._get_pickings_with_sale(invoice_values)
|
||||
|
||||
# Refund case don't included Section, Note or DownPayments
|
||||
if not sale_pickings or self._get_invoice_type() == "out_refund":
|
||||
return super()._create_invoice(invoice_values)
|
||||
|
||||
# Check Other Sale Lines
|
||||
# Section, Note and Down Payments
|
||||
section_note_lines = down_payment_lines = self.env["sale.order.line"]
|
||||
# Resequencing
|
||||
invoice_item_sequence = (
|
||||
0 # Incremental sequencing to keep the lines order on the invoice.
|
||||
)
|
||||
invoice_item_seq_dict = {}
|
||||
for pick in sale_pickings.sorted(key=lambda p: p.name):
|
||||
order = pick.sale_id.with_company(pick.sale_id.company_id)
|
||||
invoiceable_lines = order._get_invoiceable_lines(final=True)
|
||||
section_note_lines |= invoiceable_lines.filtered(
|
||||
lambda ln: ln.display_type in ("line_section", "line_note")
|
||||
)
|
||||
down_payment_lines |= invoiceable_lines.filtered(
|
||||
lambda ln: ln.is_downpayment
|
||||
)
|
||||
|
||||
# Use for Resequencing
|
||||
for line in order.order_line:
|
||||
invoice_item_seq_dict[line.id] = invoice_item_sequence
|
||||
invoice_item_sequence += 1
|
||||
|
||||
# Sections and Notes
|
||||
if section_note_lines:
|
||||
section_note_vals = []
|
||||
for line in section_note_lines:
|
||||
sale_line_vals = line._prepare_invoice_line()
|
||||
# Change [(4, 59)] for [(6, 0, [59])] to avoid error
|
||||
# in method to Resequencing
|
||||
sale_line_vals["sale_line_ids"] = [
|
||||
(6, 0, [sale_line_vals.get("sale_line_ids")[0][1]])
|
||||
]
|
||||
section_note_vals.append((0, 0, sale_line_vals))
|
||||
|
||||
invoice_values["invoice_line_ids"] += section_note_vals
|
||||
|
||||
# Resequencing, necessary in the case of Grouping Sale Orders
|
||||
for line in invoice_values.get("invoice_line_ids"):
|
||||
# [(6, 0, {})]
|
||||
if line[2]:
|
||||
sale_line = line[2].get("sale_line_ids")
|
||||
if sale_line:
|
||||
# [(6, 0, [58])]
|
||||
line[2]["sequence"] = invoice_item_seq_dict.get(sale_line[0][2][0])
|
||||
|
||||
# Down Payments
|
||||
# After the Resequencing to put it in the end of Invoice
|
||||
if down_payment_lines:
|
||||
down_payment_vals = []
|
||||
down_payment_section_added = False
|
||||
for line in down_payment_lines:
|
||||
if not down_payment_section_added and line.is_downpayment:
|
||||
# Create a dedicated section for the down payments
|
||||
# (put at the end of the invoiceable_lines)
|
||||
down_payment_vals.append(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
line.order_id._prepare_down_payment_section_line(
|
||||
sequence=invoice_item_sequence,
|
||||
),
|
||||
),
|
||||
)
|
||||
down_payment_section_added = True
|
||||
invoice_item_sequence += 1
|
||||
|
||||
if line.is_downpayment:
|
||||
down_payment_vals.append(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
line._prepare_invoice_line(
|
||||
sequence=invoice_item_sequence,
|
||||
),
|
||||
),
|
||||
)
|
||||
invoice_item_sequence += 1
|
||||
|
||||
invoice_values["invoice_line_ids"] += down_payment_vals
|
||||
|
||||
moves = (
|
||||
self.env["account.move"]
|
||||
.sudo()
|
||||
.with_context(default_move_type="out_invoice")
|
||||
.create(invoice_values)
|
||||
)
|
||||
|
||||
# param Final: if True, refunds will be generated if necessary
|
||||
final = self.deduct_down_payments
|
||||
if final:
|
||||
moves.sudo().filtered(
|
||||
lambda m: m.amount_total < 0
|
||||
).action_switch_move_type()
|
||||
for move in moves:
|
||||
move.message_post_with_source(
|
||||
source_ref="mail.message_origin_link",
|
||||
render_values={
|
||||
"self": move.picking_ids,
|
||||
"origin": move.picking_ids,
|
||||
},
|
||||
subtype_id=self.env.ref("mail.mt_note").id,
|
||||
)
|
||||
return moves
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="view_sale_stock_invoice_onshipping" model="ir.ui.view">
|
||||
<field name="name">Sale Stock Invoice Onshipping</field>
|
||||
<field name="model">stock.invoice.onshipping</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="stock_picking_invoicing.view_stock_invoice_onshipping"
|
||||
/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="group" position="after">
|
||||
<field name="has_down_payments" invisible="1" readonly="1" />
|
||||
<label
|
||||
for="deduct_down_payments"
|
||||
string=""
|
||||
invisible="not has_down_payments"
|
||||
/>
|
||||
<div invisible="not has_down_payments" id="down_payment_details">
|
||||
<field name="deduct_down_payments" nolabel="1" />
|
||||
<label for="deduct_down_payments" />
|
||||
</div>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user