Ecocentral/ecocentral#152: include in_refund invoices. rappel_pending readonly. vendor ref in invoice lines
This commit is contained in:
parent
4c07dd042d
commit
29ef56b800
5 changed files with 140 additions and 18 deletions
|
|
@ -16,13 +16,16 @@ class AccountMove(models.Model):
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
moves = super().create(vals_list)
|
moves = super().create(vals_list)
|
||||||
for move in moves:
|
for move in moves:
|
||||||
if move.move_type == "in_invoice" and move.partner_id.rappel_commission:
|
if (
|
||||||
|
move.move_type in ("in_invoice", "in_refund")
|
||||||
|
and move.partner_id.rappel_commission
|
||||||
|
):
|
||||||
move.rappel_pending = True
|
move.rappel_pending = True
|
||||||
return moves
|
return moves
|
||||||
|
|
||||||
@api.onchange("partner_id")
|
@api.onchange("partner_id")
|
||||||
def _onchange_partner_id_rappel(self):
|
def _onchange_partner_id_rappel(self):
|
||||||
if self.move_type == "in_invoice":
|
if self.move_type in ("in_invoice", "in_refund"):
|
||||||
self.rappel_pending = bool(
|
self.rappel_pending = bool(
|
||||||
self.partner_id and self.partner_id.rappel_commission
|
self.partner_id and self.partner_id.rappel_commission
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ class TestPurchaseRappel(AccountTestInvoicingCommon):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_vendor_bill(self, partner, amount=1000.0, post=True):
|
def _create_vendor_bill(self, partner, amount=1000.0, post=True, ref=None):
|
||||||
move = self.init_invoice(
|
move = self.init_invoice(
|
||||||
"in_invoice",
|
"in_invoice",
|
||||||
partner=partner,
|
partner=partner,
|
||||||
|
|
@ -77,6 +77,22 @@ class TestPurchaseRappel(AccountTestInvoicingCommon):
|
||||||
post=False,
|
post=False,
|
||||||
)
|
)
|
||||||
move.invoice_line_ids[0].price_unit = amount
|
move.invoice_line_ids[0].price_unit = amount
|
||||||
|
if ref is not None:
|
||||||
|
move.ref = ref
|
||||||
|
if post:
|
||||||
|
move.action_post()
|
||||||
|
return move
|
||||||
|
|
||||||
|
def _create_vendor_refund(self, partner, amount=200.0, post=True, ref=None):
|
||||||
|
move = self.init_invoice(
|
||||||
|
"in_refund",
|
||||||
|
partner=partner,
|
||||||
|
products=self.product_a,
|
||||||
|
post=False,
|
||||||
|
)
|
||||||
|
move.invoice_line_ids[0].price_unit = amount
|
||||||
|
if ref is not None:
|
||||||
|
move.ref = ref
|
||||||
if post:
|
if post:
|
||||||
move.action_post()
|
move.action_post()
|
||||||
return move
|
return move
|
||||||
|
|
@ -236,3 +252,82 @@ class TestPurchaseRappel(AccountTestInvoicingCommon):
|
||||||
)
|
)
|
||||||
with self.assertRaises(UserError):
|
with self.assertRaises(UserError):
|
||||||
wizard.action_create_rappel_invoices()
|
wizard.action_create_rappel_invoices()
|
||||||
|
|
||||||
|
def test_wizard_vendor_ref_in_description(self):
|
||||||
|
bill_with_ref = self._create_vendor_bill(
|
||||||
|
self.vendor_rappel, amount=1000.0, ref="VENDOR-REF-001"
|
||||||
|
)
|
||||||
|
wizard = (
|
||||||
|
self.env["account.move.rappel.wizard"]
|
||||||
|
.with_context(active_ids=bill_with_ref.ids)
|
||||||
|
.create({})
|
||||||
|
)
|
||||||
|
action = wizard.action_create_rappel_invoices()
|
||||||
|
rappel_invoice = self.env["account.move"].browse(action["res_id"])
|
||||||
|
line_name = rappel_invoice.invoice_line_ids[0].name
|
||||||
|
self.assertIn("VENDOR-REF-001", line_name)
|
||||||
|
self.assertNotIn(bill_with_ref.name, line_name)
|
||||||
|
|
||||||
|
bill_without_ref = self._create_vendor_bill(
|
||||||
|
self.vendor_rappel, amount=1000.0, ref=None
|
||||||
|
)
|
||||||
|
wizard2 = (
|
||||||
|
self.env["account.move.rappel.wizard"]
|
||||||
|
.with_context(active_ids=bill_without_ref.ids)
|
||||||
|
.create({})
|
||||||
|
)
|
||||||
|
action2 = wizard2.action_create_rappel_invoices()
|
||||||
|
rappel_invoice2 = self.env["account.move"].browse(action2["res_id"])
|
||||||
|
line_name2 = rappel_invoice2.invoice_line_ids[0].name
|
||||||
|
self.assertIn(bill_without_ref.name, line_name2)
|
||||||
|
|
||||||
|
def test_wizard_includes_in_refund(self):
|
||||||
|
bill = self._create_vendor_bill(self.vendor_rappel, amount=1000.0)
|
||||||
|
refund = self._create_vendor_refund(self.vendor_rappel, amount=200.0)
|
||||||
|
self.assertTrue(refund.rappel_pending)
|
||||||
|
|
||||||
|
wizard = (
|
||||||
|
self.env["account.move.rappel.wizard"]
|
||||||
|
.with_context(active_ids=(bill + refund).ids)
|
||||||
|
.create({})
|
||||||
|
)
|
||||||
|
action = wizard.action_create_rappel_invoices()
|
||||||
|
rappel_invoice = self.env["account.move"].browse(action["res_id"])
|
||||||
|
|
||||||
|
self.assertEqual(len(rappel_invoice.invoice_line_ids), 2)
|
||||||
|
amounts = sorted(rappel_invoice.invoice_line_ids.mapped("price_unit"))
|
||||||
|
self.assertEqual(amounts, [-10.0, 50.0])
|
||||||
|
self.assertFalse(bill.rappel_pending)
|
||||||
|
self.assertFalse(refund.rappel_pending)
|
||||||
|
|
||||||
|
def test_wizard_refund_subtracts_commission(self):
|
||||||
|
bill = self._create_vendor_bill(self.vendor_rappel, amount=1000.0)
|
||||||
|
refund = self._create_vendor_refund(self.vendor_rappel, amount=200.0)
|
||||||
|
|
||||||
|
wizard = (
|
||||||
|
self.env["account.move.rappel.wizard"]
|
||||||
|
.with_context(active_ids=(bill + refund).ids)
|
||||||
|
.create({})
|
||||||
|
)
|
||||||
|
action = wizard.action_create_rappel_invoices()
|
||||||
|
rappel_invoice = self.env["account.move"].browse(action["res_id"])
|
||||||
|
|
||||||
|
self.assertEqual(len(rappel_invoice.invoice_line_ids), 2)
|
||||||
|
line_by_sign = {
|
||||||
|
line.price_unit > 0: line for line in rappel_invoice.invoice_line_ids
|
||||||
|
}
|
||||||
|
self.assertEqual(line_by_sign[True].price_unit, 50.0)
|
||||||
|
self.assertEqual(line_by_sign[False].price_unit, -10.0)
|
||||||
|
self.assertEqual(rappel_invoice.amount_untaxed, 40.0)
|
||||||
|
self.assertIn("Refund", line_by_sign[False].name)
|
||||||
|
|
||||||
|
def test_rappel_pending_set_on_refund_create(self):
|
||||||
|
refund = self._create_vendor_refund(
|
||||||
|
self.vendor_rappel, amount=200.0, post=False
|
||||||
|
)
|
||||||
|
self.assertTrue(refund.rappel_pending)
|
||||||
|
|
||||||
|
refund_no_rappel = self._create_vendor_refund(
|
||||||
|
self.vendor_no_rappel, amount=200.0, post=False
|
||||||
|
)
|
||||||
|
self.assertFalse(refund_no_rappel.rappel_pending)
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@
|
||||||
<field name="to_check" position="after">
|
<field name="to_check" position="after">
|
||||||
<field name="rappel_pending"
|
<field name="rappel_pending"
|
||||||
optional="show"
|
optional="show"
|
||||||
|
readonly="1"
|
||||||
widget="boolean_toggle"
|
widget="boolean_toggle"
|
||||||
column_invisible="context.get('default_move_type') != 'in_invoice'"/>
|
column_invisible="context.get('default_move_type') not in ('in_invoice', 'in_refund')"/>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -40,7 +41,8 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="to_check" position="after">
|
<field name="to_check" position="after">
|
||||||
<field name="rappel_pending"
|
<field name="rappel_pending"
|
||||||
invisible="move_type != 'in_invoice'"
|
invisible="move_type not in ('in_invoice', 'in_refund')"
|
||||||
|
readonly="1"
|
||||||
widget="boolean_toggle"/>
|
widget="boolean_toggle"/>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
||||||
|
|
@ -49,16 +49,17 @@ class AccountMoveRappelWizard(models.TransientModel):
|
||||||
active_ids = self.env.context.get("active_ids", [])
|
active_ids = self.env.context.get("active_ids", [])
|
||||||
moves = self.env["account.move"].browse(active_ids)
|
moves = self.env["account.move"].browse(active_ids)
|
||||||
|
|
||||||
# Filter only posted vendor bills
|
# Filter only posted vendor bills and refunds
|
||||||
valid_moves = moves.filtered(
|
valid_moves = moves.filtered(
|
||||||
lambda m: m.move_type == "in_invoice" and m.state == "posted"
|
lambda m: m.move_type in ("in_invoice", "in_refund")
|
||||||
|
and m.state == "posted"
|
||||||
)
|
)
|
||||||
if not valid_moves:
|
if not valid_moves:
|
||||||
raise UserError(
|
raise UserError(
|
||||||
_(
|
_(
|
||||||
"No posted vendor bills found in the selection. "
|
"No posted vendor bills or refunds found in the selection. "
|
||||||
"Only posted (validated) vendor bills can be used to create "
|
"Only posted (validated) vendor bills and refunds can be used to "
|
||||||
"rappel commission invoices."
|
"create rappel commission invoices."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -135,11 +136,28 @@ class AccountMoveRappelWizard(models.TransientModel):
|
||||||
for bill in bills:
|
for bill in bills:
|
||||||
taxable_base = bill.amount_untaxed
|
taxable_base = bill.amount_untaxed
|
||||||
commission_amount = taxable_base * rate / 100.0
|
commission_amount = taxable_base * rate / 100.0
|
||||||
|
vendor_ref = bill.ref or bill.name
|
||||||
|
if bill.move_type == "in_refund":
|
||||||
|
# Vendor refunds reduce the commission: negate the amount so
|
||||||
|
# the resulting line subtracts from the rappel invoice total.
|
||||||
|
# amount_untaxed is always positive in Odoo, so we flip the
|
||||||
|
# sign here and display a negative base in the description.
|
||||||
|
commission_amount = -commission_amount
|
||||||
|
display_base = -taxable_base
|
||||||
|
name = _(
|
||||||
|
"Rappel commission %(rate)s%% — Refund %(bill)s "
|
||||||
|
"(taxable base: %(base)s %(currency)s)",
|
||||||
|
rate=rate,
|
||||||
|
bill=vendor_ref,
|
||||||
|
base=f"{display_base:,.2f}",
|
||||||
|
currency=bill.currency_id.name,
|
||||||
|
)
|
||||||
|
else:
|
||||||
name = _(
|
name = _(
|
||||||
"Rappel commission %(rate)s%% — Bill %(bill)s "
|
"Rappel commission %(rate)s%% — Bill %(bill)s "
|
||||||
"(taxable base: %(base)s %(currency)s)",
|
"(taxable base: %(base)s %(currency)s)",
|
||||||
rate=rate,
|
rate=rate,
|
||||||
bill=bill.name,
|
bill=vendor_ref,
|
||||||
base=f"{taxable_base:,.2f}",
|
base=f"{taxable_base:,.2f}",
|
||||||
currency=bill.currency_id.name,
|
currency=bill.currency_id.name,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@
|
||||||
<field name="move_ids">
|
<field name="move_ids">
|
||||||
<tree string="Vendor Bills" editable="0">
|
<tree string="Vendor Bills" editable="0">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="move_type"
|
||||||
|
widget="badge"
|
||||||
|
decoration-info="move_type == 'in_invoice'"
|
||||||
|
decoration-danger="move_type == 'in_refund'"/>
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
<field name="invoice_date"/>
|
<field name="invoice_date"/>
|
||||||
<field name="amount_untaxed"
|
<field name="amount_untaxed"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue