Skip to content

Commit f50e5d8

Browse files
PHATTARAPON.Kclaude
andcommitted
[16.0][ADD] purchase_guarantee_disbursement: guarantee refund via disbursement
Add new module to enable refunding purchase guarantees through the disbursement system. Integrates guarantee records with disbursement requests, enabling approval workflows (draft → submitted → validated → bill creation). When a disbursement bill is created, it automatically links to guarantee.bill_ids, updating amount_returned. Supports partial refunds via multiple disbursement requests. Follows the pattern of purchase_order_disbursement module. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 85b42af commit f50e5d8

8 files changed

Lines changed: 217 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "Purchase Guarantee Disbursement",
3+
"version": "16.0.1.0.0",
4+
"summary": "Create disbursement requests to refund purchase guarantees",
5+
"author": "KMITL",
6+
"website": "https://www.kmitl.ac.th",
7+
"category": "KMITL",
8+
"depends": ["purchase_guarantee_kmitl", "disbursement"],
9+
"data": [
10+
"security/ir.model.access.csv",
11+
"views/disbursement_request_views.xml",
12+
"views/purchase_guarantee_views.xml",
13+
],
14+
"installable": True,
15+
"auto_install": False,
16+
"license": "LGPL-3",
17+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import disbursement_request
2+
from . import purchase_guarantee
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from odoo import _, fields, models
2+
from odoo.exceptions import UserError
3+
4+
5+
class DisbursementRequest(models.Model):
6+
_inherit = "disbursement.request"
7+
8+
guarantee_id = fields.Many2one(
9+
comodel_name="purchase.guarantee",
10+
string="Purchase Guarantee",
11+
ondelete="set null",
12+
index=True,
13+
tracking=True,
14+
)
15+
16+
def _create_bill(self):
17+
bill = super()._create_bill()
18+
if self.guarantee_id:
19+
self.guarantee_id.write({"bill_ids": [(4, bill.id)]})
20+
return bill
21+
22+
def action_view_purchase_guarantee(self):
23+
self.ensure_one()
24+
if not self.guarantee_id:
25+
raise UserError(_("No Purchase Guarantee linked to this request."))
26+
return {
27+
"type": "ir.actions.act_window",
28+
"name": _("Purchase Guarantee"),
29+
"res_model": "purchase.guarantee",
30+
"res_id": self.guarantee_id.id,
31+
"view_mode": "form",
32+
"target": "current",
33+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from odoo import Command, _, api, fields, models
2+
from odoo.exceptions import UserError
3+
4+
5+
class PurchaseGuarantee(models.Model):
6+
_inherit = "purchase.guarantee"
7+
8+
disbursement_request_ids = fields.One2many(
9+
comodel_name="disbursement.request",
10+
inverse_name="guarantee_id",
11+
string="Disbursement Requests",
12+
)
13+
disbursement_request_count = fields.Integer(
14+
compute="_compute_disbursement_request",
15+
)
16+
is_disbursement_refund_allowed = fields.Boolean(
17+
compute="_compute_disbursement_request",
18+
)
19+
hide_create_disbursement_button = fields.Boolean(
20+
compute="_compute_hide_create_disbursement_button",
21+
)
22+
23+
@api.depends("disbursement_request_ids", "amount", "amount_returned")
24+
def _compute_disbursement_request(self):
25+
for rec in self:
26+
rec.disbursement_request_count = len(rec.disbursement_request_ids)
27+
rec.is_disbursement_refund_allowed = (rec.amount - rec.amount_returned) > 0
28+
29+
@api.depends("state", "is_disbursement_refund_allowed")
30+
def _compute_hide_create_disbursement_button(self):
31+
for rec in self:
32+
rec.hide_create_disbursement_button = (
33+
rec.state != "lock" or not rec.is_disbursement_refund_allowed
34+
)
35+
36+
def _prepare_disbursement_request_vals(self):
37+
self.ensure_one()
38+
return {
39+
"guarantee_id": self.id,
40+
"partner_id": self.partner_id.id,
41+
"ref": self.name,
42+
"analytic_distribution": self.analytic_distribution,
43+
"line_ids": [
44+
Command.create(
45+
{
46+
"name": _("คืนเงินหลักประกัน - %s") % self.guarantee_method_id.name,
47+
"account_id": self.guarantee_method_id.account_id.id,
48+
"quantity": 1.0,
49+
"price_unit": self.amount - self.amount_returned,
50+
"analytic_distribution": self.analytic_distribution,
51+
}
52+
)
53+
],
54+
}
55+
56+
def action_create_disbursement_refund(self):
57+
self.ensure_one()
58+
if self.amount - self.amount_returned <= 0:
59+
raise UserError(_("ไม่มียอดเงินหลักประกันคงเหลือที่จะคืน"))
60+
disbursement = self.env["disbursement.request"].create(
61+
self._prepare_disbursement_request_vals()
62+
)
63+
return {
64+
"type": "ir.actions.act_window",
65+
"res_model": "disbursement.request",
66+
"res_id": disbursement.id,
67+
"view_mode": "form",
68+
"target": "current",
69+
}
70+
71+
def action_view_disbursement_requests(self):
72+
self.ensure_one()
73+
requests = self.disbursement_request_ids
74+
if len(requests) == 1:
75+
return {
76+
"type": "ir.actions.act_window",
77+
"name": _("Disbursement Request"),
78+
"res_model": "disbursement.request",
79+
"res_id": requests.id,
80+
"view_mode": "form",
81+
"target": "current",
82+
}
83+
return {
84+
"type": "ir.actions.act_window",
85+
"name": _("Disbursement Requests"),
86+
"res_model": "disbursement.request",
87+
"view_mode": "tree,form",
88+
"domain": [("id", "in", requests.ids)],
89+
"target": "current",
90+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2+
access_disbursement_request_purchase_guarantee_user,disbursement.request.purchase.guarantee.user,disbursement.model_disbursement_request,purchase.group_purchase_user,1,1,0,0
3+
access_disbursement_request_line_purchase_guarantee_user,disbursement.request.line.purchase.guarantee.user,disbursement.model_disbursement_request_line,purchase.group_purchase_user,1,1,0,0
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
<record id="view_disbursement_request_form" model="ir.ui.view">
4+
<field name="name">disbursement.request.form.guarantee</field>
5+
<field name="model">disbursement.request</field>
6+
<field
7+
name="inherit_id"
8+
ref="disbursement.view_disbursement_request_form"
9+
/>
10+
<field name="arch" type="xml">
11+
<xpath expr="//field[@name='state']" position="after">
12+
<field name="guarantee_id" invisible="1"/>
13+
</xpath>
14+
<xpath expr="//div[@name='button_box']" position="inside">
15+
<button
16+
name="action_view_purchase_guarantee"
17+
type="object"
18+
class="oe_stat_button"
19+
icon="fa-shield"
20+
string="Purchase Guarantee"
21+
attrs="{'invisible': [('guarantee_id', '=', False)]}"
22+
/>
23+
</xpath>
24+
</field>
25+
</record>
26+
</odoo>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
<record id="view_purchase_guarantee_form" model="ir.ui.view">
4+
<field name="name">purchase.guarantee.form.disbursement</field>
5+
<field name="model">purchase.guarantee</field>
6+
<field
7+
name="inherit_id"
8+
ref="purchase_guarantee_kmitl.view_purchase_guarantee_form"
9+
/>
10+
<field name="arch" type="xml">
11+
<!-- Hidden fields -->
12+
<xpath expr="//div[@name='button_box']" position="before">
13+
<field name="hide_create_disbursement_button" invisible="1"/>
14+
</xpath>
15+
16+
<!-- Refund button in header -->
17+
<xpath expr="//button[@name='button_lock']" position="after">
18+
<button
19+
name="action_create_disbursement_refund"
20+
string="คืนเงินหลักประกัน"
21+
type="object"
22+
class="oe_highlight"
23+
attrs="{'invisible': [('hide_create_disbursement_button', '=', True)]}"
24+
/>
25+
</xpath>
26+
27+
<!-- Smart button -->
28+
<xpath expr="//div[@name='button_box']" position="inside">
29+
<button
30+
name="action_view_disbursement_requests"
31+
type="object"
32+
class="oe_stat_button"
33+
icon="fa-exchange"
34+
attrs="{'invisible': [('disbursement_request_count', '=', 0)]}"
35+
>
36+
<field
37+
name="disbursement_request_count"
38+
widget="statinfo"
39+
string="Disbursement"
40+
/>
41+
</button>
42+
</xpath>
43+
</field>
44+
</record>
45+
</odoo>

0 commit comments

Comments
 (0)