Skip to content

Commit

Permalink
[MIG] purchase_stock_picking_return_invoicing: Migration to 14.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mariadforgeflow authored and MiquelRForgeFlow committed Aug 25, 2021
1 parent 7e57873 commit b9a78f6
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 85 deletions.
8 changes: 4 additions & 4 deletions purchase_stock_picking_return_invoicing/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Copyright 2019 Eficent Business and IT Consulting Services
# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com)
# Copyright 2017-2018 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Purchase Stock Picking Return Invoicing",
"summary": "Add an option to refund returned pickings",
"version": "13.0.1.0.0",
"version": "14.0.1.0.0",
"category": "Purchases",
"website": "https://github.com/OCA/account-invoicing",
"author": "Eficent," "Tecnativa," "Odoo Community Association (OCA)",
"author": "ForgeFlow, Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3",
"installable": True,
"development_status": "Mature",
"depends": ["purchase_stock"],
"data": ["views/account_invoice_view.xml", "views/purchase_view.xml"],
"maintainers": ["pedrobaeza"],
"maintainers": ["pedrobaeza", "MiquelRForgeFlow"],
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2017 Eficent Business and IT Consulting Services
# Copyright 2017 ForgeFlow S.L. (https://www.forgeflow.com)
# Copyright 2017-2018 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

Expand All @@ -13,7 +13,7 @@ class AccountMove(models.Model):
def _onchange_purchase_auto_complete(self):
"""Remove lines with qty=0 when making refunds."""
res = super()._onchange_purchase_auto_complete()
if self.type == "in_refund":
if self.move_type == "in_refund":
self.line_ids -= self.invoice_line_ids.filtered(
lambda x: float_is_zero(
x.quantity, precision_rounding=x.product_uom_id.rounding
Expand Down
173 changes: 128 additions & 45 deletions purchase_stock_picking_return_invoicing/models/purchase_order.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Copyright 2017 Eficent Business and IT Consulting Services
# Copyright 2017 ForgeFlow S.L. (https://www.forgeflow.com)
# Copyright 2018 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import collections
from itertools import groupby

from odoo import api, fields, models
from odoo.tools import float_compare
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare, float_is_zero


class PurchaseOrder(models.Model):
Expand Down Expand Up @@ -45,7 +47,7 @@ def _get_invoiced(self):
def _compute_invoice_refund_count(self):
for order in self:
invoices = order.mapped("order_line.invoice_lines.move_id").filtered(
lambda x: x.type == "in_refund"
lambda x: x.move_type == "in_refund"
)
order.invoice_refund_count = len(invoices)

Expand All @@ -60,45 +62,129 @@ def _compute_invoice(self):
for order in self:
order.invoice_count -= order.invoice_refund_count

def action_view_invoice_refund(self):
def action_create_invoice_refund(self):
"""Create the refund associated to the PO."""
precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
# 1) Prepare refund vals and clean-up the section lines
invoice_vals_list = []
for order in self:
if order.invoice_status != "to invoice":
continue
order = order.with_company(order.company_id)
pending_section = None
# Invoice values.
invoice_vals = order._prepare_invoice()
# Invoice line values (keep only necessary sections).
for line in order.order_line:
if line.display_type == "line_section":
pending_section = line
continue
if not float_is_zero(line.qty_to_invoice, precision_digits=precision):
if pending_section:
invoice_vals["invoice_line_ids"].append(
(0, 0, pending_section._prepare_account_move_line())
)
pending_section = None
invoice_vals["invoice_line_ids"].append(
(0, 0, line._prepare_account_move_line())
)
invoice_vals_list.append(invoice_vals)
if not invoice_vals_list:
raise UserError(
_(
"There is no invoiceable line. "
"If a product has a control policy based on received quantity, "
"please make sure that a quantity has been received."
)
)
# 2) group by (company_id, partner_id, currency_id) for batch creation
new_invoice_vals_list = []
for _grouping_keys, invoices in groupby(
invoice_vals_list,
key=lambda x: (
x.get("company_id"),
x.get("partner_id"),
x.get("currency_id"),
),
):
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
# 3) Create refunds.
moves = self.env["account.move"]
AccountMove = self.env["account.move"].with_context(
default_move_type="in_refund"
)
for vals in invoice_vals_list:
moves |= AccountMove.with_company(vals["company_id"]).create(vals)
return self.action_view_invoice_refund(moves)

def action_view_invoice_refund(self, invoices=False):
"""This function returns an action that display existing vendor refund
bills of given purchase order id.
bills of given purchase order ids.
When only one found, show the vendor bill immediately.
"""
action = self.env.ref("account.action_move_in_refund_type")
result = action.read()[0]
create_refund = self.env.context.get("create_refund", False)
refunds = self.invoice_ids.filtered(lambda x: x.type == "in_refund")
# override the context to get rid of the default filtering
result["context"] = {
"default_type": "in_refund",
"default_company_id": self.company_id.id,
"default_purchase_id": self.id,
}
if not invoices:
# Invoice_ids may be filtered depending on the user. To ensure we get all
# invoices related to the purchase order, we read them in sudo to fill the
# cache.
self.sudo()._read(["invoice_ids"])
invoices = self.invoice_ids
refunds = invoices.filtered(lambda x: x.move_type == "in_refund")
result = self.env["ir.actions.act_window"]._for_xml_id(
"account.action_move_in_refund_type"
)
# choose the view_mode accordingly
if len(refunds) > 1 and not create_refund:
result["domain"] = "[('id', 'in', " + str(refunds.ids) + ")]"
if len(refunds) > 1:
result["domain"] = [("id", "in", refunds.ids)]
elif len(refunds) == 1:
res = self.env.ref("account.view_move_form", False)
form_view = [(res and res.id or False, "form")]
if "views" in result:
result["views"] = form_view + [
(state, view) for state, view in result["views"] if view != "form"
]
else:
result["views"] = form_view
result["res_id"] = refunds.id
else:
res = self.env.ref("account.invoice_supplier_form", False)
result["views"] = [(res and res.id or False, "form")]
# Do not set an move_id if we want to create a new refund.
if not create_refund:
result["res_id"] = refunds.id or False
result["context"]["default_origin"] = self.name
result["context"]["default_reference"] = self.partner_ref
result = {"type": "ir.actions.act_window_close"}
return result

def action_view_invoice(self):
def action_view_invoice(self, invoices=False):
"""Change super action for displaying only normal invoices."""
result = super(PurchaseOrder, self).action_view_invoice()
invoices = self.invoice_ids.filtered(lambda x: x.type == "in_invoice")
# choose the view_mode accordingly
if len(invoices) != 1:
result["domain"] = [("id", "in", invoices.ids)]
elif len(invoices) == 1:
res = self.env.ref("account.view_move_form", False)
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = invoices.id
if not invoices:
# Invoice_ids may be filtered depending on the user. To ensure we get all
# invoices related to the purchase order, we read them in sudo to fill the
# cache.
self.sudo()._read(["invoice_ids"])
invoices = self.invoice_ids
invoices = invoices.filtered(lambda x: x.move_type == "in_invoice")
result = super(PurchaseOrder, self).action_view_invoice(invoices)
return result


Expand All @@ -118,8 +204,8 @@ def _compute_qty_refunded(self):
for line in self:
inv_lines = line.invoice_lines.filtered(
lambda x: (
(x.move_id.type == "in_invoice" and x.quantity < 0.0)
or (x.move_id.type == "in_refund" and x.quantity > 0.0)
(x.move_id.move_type == "in_invoice" and x.quantity < 0.0)
or (x.move_id.move_type == "in_refund" and x.quantity > 0.0)
)
)
line.qty_refunded = sum(
Expand Down Expand Up @@ -160,14 +246,11 @@ def _compute_qty_returned(self):
for line in self:
line.qty_returned = line_qtys.get(line.id, 0)

def _prepare_account_move_line(self, move):
def _prepare_account_move_line(self, move=None):
data = super()._prepare_account_move_line(move)
if self.product_id.purchase_method == "receive":
# This formula proceeds from the simplification of full expression:
# qty_received + qty_returned - (qty_invoiced + qty_refunded) -
# (qty_returned - qty_refunded)
qty = self.qty_received - self.qty_invoiced
data["quantity"] = qty
if move.type == "in_refund":
move_type = self.env.context.get("default_move_type", False)
if (move and move.move_type == "in_refund") or (
not move and move_type and move_type == "in_refund"
):
data["quantity"] *= -1.0
return data
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
* Eficent <http://www.eficent.com>:
* Forgeflow <https://www.forgeflow.com>:

* Jordi Ballester Alomar <jordi.ballester@eficent.com>
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com>

* Tecnativa <https://www.tecnativa.com>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# Copyright 2019 Eficent Business and IT Consulting Services
# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com)
# Copyright 2017-2018 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields
from odoo.tests.common import Form, SavepointCase
from odoo.tests.common import SavepointCase, tagged


@tagged("post_install", "-at_install")
class TestPurchaseStockPickingReturnInvoicing(SavepointCase):
at_install = False
post_install = True

@classmethod
def setUpClass(cls):
"""Add some defaults to let the test run without an accounts chart."""
Expand Down Expand Up @@ -86,6 +84,7 @@ def setUpClass(cls):
)
cls.po_line = cls.po.order_line
cls.po.button_confirm()
cls.po.button_approve()

def check_values(
self,
Expand Down Expand Up @@ -118,13 +117,8 @@ def test_purchase_stock_return_1(self):
pick.button_validate()
self.check_values(self.po_line, 0, 5, 0, 0, "to invoice")
# Make invoice
ctx = self.po.action_view_invoice()["context"]
active_model = self.env["account.move"].with_context(ctx)
view_id = "account.view_move_form"
with Form(active_model, view=view_id) as f:
f.partner_id = self.partner
f.purchase_id = self.po
inv_1 = f.save()
action = self.po.action_create_invoice()
inv_1 = self.env["account.move"].browse(action["res_id"])
self.check_values(self.po_line, 0, 5, 0, 5, "invoiced")
self.assertAlmostEqual(inv_1.amount_untaxed_signed, -50, 2)

Expand All @@ -137,14 +131,12 @@ def test_purchase_stock_return_1(self):
return_pick.button_validate()
self.check_values(self.po_line, 2, 3, 0, 5, "to invoice")
# Make refund
ctx = self.po.action_view_invoice_refund()["context"]
active_model = self.env["account.move"].with_context(ctx)
view_id = "account.view_move_form"
with Form(active_model, view=view_id) as f:
f.partner_id = self.partner
f.purchase_id = self.po
inv_2 = f.save()
action2 = self.po.with_context(
default_move_type="in_refund"
).action_create_invoice_refund()
inv_2 = self.env["account.move"].browse(action2["res_id"])
self.check_values(self.po_line, 2, 3, 2, 3, "invoiced")

self.assertAlmostEqual(inv_2.amount_untaxed_signed, 20, 2)
action = self.po.action_view_invoice()
self.assertEqual(action["res_id"], inv_1.id)
Expand All @@ -171,12 +163,7 @@ def test_purchase_stock_return_2(self):
return_pick.button_validate()
self.check_values(self.po_line, 2, 3, 0, 0, "to invoice")
# Make invoice
ctx = self.po.action_view_invoice()["context"]
active_model = self.env["account.move"].with_context(ctx)
view_id = "account.view_move_form"
with Form(active_model, view=view_id) as f:
f.partner_id = self.partner
f.purchase_id = self.po
inv_1 = f.save()
action = self.po.action_create_invoice()
inv_1 = self.env["account.move"].browse(action["res_id"])
self.check_values(self.po_line, 2, 3, 0, 3, "invoiced")
self.assertAlmostEqual(inv_1.amount_untaxed_signed, -30, 2)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<field name="invoice_vendor_bill_id" position="after">
<field
name="purchase_vendor_bill_id"
attrs="{'invisible': ['|', '|', ('state','not in',['draft']), ('state', '=', 'purchase'), ('type', '=', 'in_invoice')]}"
attrs="{'invisible': ['|', '|', ('state','not in',['draft']), ('state', '=', 'purchase'), ('move_type', '=', 'in_invoice')]}"
class="oe_edit_only"
domain="[('company_id', '=', company_id), ('partner_id','child_of', [partner_id])]"
placeholder="Select a purchase order or an old bill"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
ref="purchase_stock.purchase_order_view_form_inherit"
/>
<field name="arch" type="xml">
<button name="action_view_invoice" position="after">
<button name="action_create_invoice" position="after">
<button
name="action_view_invoice_refund"
name="action_create_invoice_refund"
string="Create Refund"
type="object"
class="oe_stat_button"
context="{'create_refund':True}"
context="{'create_refund':True, 'default_move_type': 'in_refund'}"
attrs="{'invisible': ['|', ('state', 'not in', ('purchase', 'done')), ('invoice_status', 'in', ('no', 'invoiced'))]}"
/>
</button>
Expand All @@ -27,7 +26,7 @@
name="action_view_invoice_refund"
class="oe_stat_button"
icon="fa-pencil-square-o"
attrs="{'invisible':['|', ('invoice_count', '=', 0), ('state', 'in', ('draft','sent','to approve'))]}"
attrs="{'invisible':['|', ('invoice_refund_count', '=', 0), ('state', 'in', ('draft','sent','to approve'))]}"
>
<field
name="invoice_refund_count"
Expand Down

0 comments on commit b9a78f6

Please sign in to comment.