-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] osi_partner_credit_limit: add module
- Loading branch information
Showing
28 changed files
with
839 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg | ||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html | ||
:alt: License: AGPL-3 | ||
|
||
======== | ||
Overview | ||
======== | ||
|
||
This module prevents users from shippings orders that would put customers | ||
above their credit limit based on open invoices. | ||
|
||
It adds a new group that allows certain users to manage credit hold of the | ||
customers. | ||
|
||
After this module is installed, credit limits will be verified for all | ||
customers. | ||
|
||
Configuration | ||
============= | ||
|
||
* Add users to the 'Credit Hold' group | ||
|
||
Usage | ||
===== | ||
|
||
* Sales Hold on Customer: Disallows new sales orders and prevents confirmation | ||
of existing quotations. | ||
|
||
* Credit Hold on Customer: Allows new sales orders, confirmation of orders, | ||
but prevents orders from being shipped. | ||
|
||
* Credit Limit: Maximum allowed receivable balance for a customer. | ||
|
||
* Grace period: Time allowed for the customer to make payment after the term | ||
has expired. | ||
|
||
* Credit Override: When set on sale order, allows shipment on the sale order | ||
to be processed even if there is a hold on the sale order. | ||
|
||
* Credit Override requires special permission to be set on the user. | ||
|
||
Credits | ||
======= | ||
|
||
Contributors | ||
------------ | ||
|
||
* OSI Dev Team <[email protected]> | ||
* Sandeep Mangukiya <[email protected]> | ||
* Maxime Chambreuil <[email protected]> | ||
* Bhavesh Odedra <[email protected]> | ||
* Balaji Kannan <[email protected]> | ||
* Hardik Suthar <[email protected]> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Copyright (C) 2019 - 2021, Open Source Integrators | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Copyright (C) 2019 - 2021, Open Source Integrators | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
{ | ||
"name": "OSI Partner Credit Limit", | ||
"version": "17.0.1.0.0", | ||
"license": "AGPL-3", | ||
"author": "Open Source Integrators", | ||
"category": "Sales", | ||
"maintainer": "Open Source Integrators", | ||
"summary": "Enforce Partner Credit Limit", | ||
"website": "https://github.com/ursais/osi-addons", | ||
"depends": [ | ||
"sale", | ||
"sale_stock", | ||
"stock", | ||
], | ||
"data": [ | ||
"security/osi_partner_credit_limit.xml", | ||
"data/picking_data.xml", | ||
"views/res_partner.xml", | ||
"views/sale.xml", | ||
"views/stock.xml", | ||
], | ||
"installable": True, | ||
"maintainers": ["bodedra"], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?xml version="1.0" encoding='UTF-8'?> | ||
<odoo> | ||
|
||
<!-- Scheduler to update customer hold --> | ||
<record model="ir.cron" id="credit_hold_out_picking_cron"> | ||
<field name="name">Customer Credit Hold on Delivery Orders</field> | ||
<field name="interval_number">1</field> | ||
<field name="interval_type">days</field> | ||
<field name="numbercall">-1</field> | ||
<field name="doall" eval="False"/> | ||
<field name="model_id" ref="model_stock_picking"/> | ||
<field name="code">model.compute_customer_hold()</field> | ||
<field name="state">code</field> | ||
<field eval="True" name="active" /> | ||
</record> | ||
|
||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Copyright (C) 2019 - 2021, Open Source Integrators | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from . import res_partner | ||
from . import sale_order | ||
from . import stock_picking |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
# Copyright (C) 2019 - 2021, Open Source Integrators | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from datetime import datetime, timedelta | ||
|
||
from odoo import fields, models | ||
|
||
import logging | ||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class Partner(models.Model): | ||
_inherit = "res.partner" | ||
|
||
sales_hold = fields.Boolean( | ||
string="Sales Hold", | ||
default=False, | ||
help="If checked, new quotations cannot be confirmed", | ||
) | ||
grace_period = fields.Integer( | ||
string="Grace Period", | ||
help="Grace period added on top of the customer \ | ||
payment term " | ||
"(in days)", | ||
) | ||
credit_hold = fields.Boolean( | ||
string="Credit Hold", | ||
help="Place the customer on credit hold to prevent \ | ||
from shipping goods", | ||
) | ||
credit_used = fields.Monetary(string="Credit Used", compute="calculate_credit") | ||
credit_available = fields.Monetary( | ||
string="Credit Available", compute="calculate_credit" | ||
) | ||
ship_hold_days = fields.Integer( | ||
string="Customer Credit Period", | ||
help="Period past scheduled date for customer hold to verify credit card authorization", | ||
) | ||
def get_existing_invoice_balance(self, partner_id): | ||
# Open invoices (unpaid or partially paid invoices -- | ||
# It is already included in partner.credit | ||
invoice_ids = self.env["account.move"].search( | ||
[ | ||
("partner_id", "=", partner_id.id), | ||
# ('state', '=', 'draft'), | ||
("state", "in", ["open", "posted"]), | ||
("payment_state", "in", ["not_paid", "partial"]), | ||
("move_type", "in", ["out_invoice", "out_refund"]), | ||
] | ||
) | ||
# Invoices that are open (also shows up as part of partner. | ||
# Credit, so must be deducted | ||
now = fields.Datetime.to_string(datetime.now()) | ||
grace_period = timedelta(days=partner_id.grace_period) | ||
existing_invoice_balance = sum( | ||
invoice_ids.filtered( | ||
lambda inv: ( | ||
inv.invoice_date_due | ||
or inv.date_invoice | ||
or inv.create_date + grace_period > now | ||
) | ||
).mapped("amount_residual") | ||
) | ||
|
||
return existing_invoice_balance | ||
|
||
def get_existing_order_balance(self, partner_id): | ||
# Other orders for this partner | ||
order_ids = self.env["sale.order"].search( | ||
[ | ||
("partner_id", "=", partner_id.id), | ||
("state", "=", "sale"), | ||
("invoice_status", "!=", "invoiced"), | ||
] | ||
) | ||
|
||
# Confirmed orders - invoiced - draft or open / not invoiced | ||
existing_order_balance = sum(order_ids.mapped("amount_total")) | ||
|
||
return existing_order_balance | ||
|
||
|
||
def calculate_credit(self): | ||
for partner_id in self: | ||
existing_order_balance = self.get_existing_order_balance(partner_id) | ||
existing_invoice_balance = self.get_existing_invoice_balance(partner_id) | ||
|
||
# All open sale orders + partner credit (AR balance) - | ||
# Open invoices (already included in partner credit) | ||
partner_id.credit_used = existing_invoice_balance + existing_order_balance | ||
partner_id.credit_available = ( | ||
partner_id.credit_limit - partner_id.credit_used | ||
) | ||
def write(self, vals): | ||
res = super(Partner, self).write(vals) | ||
if "credit_limit" or "credit_hold" in vals: | ||
for partner in self: | ||
order_ids = self.env["sale.order"].search( | ||
[("partner_id", "=", partner.id)] | ||
) | ||
# only if partner is on credit hold, set sale orders on ship hold immediately | ||
ship_hold = partner.credit_hold | ||
|
||
# check for credit_limit | ||
if partner.credit_limit > 0 and order_ids: | ||
if not ship_hold and not self.check_limit(order_ids[0]): | ||
ship_hold = False | ||
else: | ||
ship_hold = True | ||
|
||
order_ids.write({"ship_hold": ship_hold}) | ||
|
||
# user reset credit authorization days | ||
if "ship_hold_days" in vals: | ||
pickings = self.env["stock.picking"].search( | ||
[ | ||
("picking_type_code", "=", "outgoing"), | ||
("partner_id.parent_id", "=", self.id), | ||
("state", "in", ("assigned", "confirmed", "waiting")), | ||
] | ||
) | ||
pickings.compute_customer_hold() | ||
|
||
return res | ||
|
||
def check_limit(self, sale_id): | ||
partner_id = sale_id.partner_id | ||
# Confirmed orders - invoiced - draft or open / not invoiced | ||
existing_order_balance = self.get_existing_order_balance(partner_id) | ||
existing_invoice_balance = self.get_existing_invoice_balance(partner_id) | ||
|
||
# All open sale orders + partner credit (AR balance) - | ||
# Open invoices (already included in partner credit) | ||
if ( | ||
partner_id.credit_limit | ||
and (existing_invoice_balance + existing_order_balance) | ||
> partner_id.credit_limit | ||
): | ||
return True | ||
else: | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Copyright (C) 2019 - 2021, Open Source Integrators | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import _, fields, models | ||
from odoo.exceptions import ValidationError | ||
|
||
|
||
class SaleOrder(models.Model): | ||
_inherit = "sale.order" | ||
|
||
sales_hold = fields.Boolean( | ||
related="partner_id.sales_hold", string="Customer Sales Hold" | ||
) | ||
credit_hold = fields.Boolean( | ||
related="partner_id.credit_hold", string="Customer Credit Hold" | ||
) | ||
ship_hold = fields.Boolean(string="Delivery Hold", copy=False) | ||
credit_override = fields.Boolean( | ||
string="Override Hold", tracking=True, default=False | ||
) | ||
ship_hold_days = fields.Integer( | ||
related="partner_id.ship_hold_days", | ||
help="Period past scheduled date for customer hold to verify credit card authorization", | ||
) | ||
|
||
def action_confirm(self): | ||
state = self.partner_id.check_limit(self) | ||
if not state: | ||
self.ship_hold = False | ||
if self.sales_hold and not self.credit_override: | ||
message = _("""Cannot confirm Order! The customer is on sales hold.""") | ||
# Display that the customer is on sales hold | ||
raise ValidationError(message) | ||
elif self.ship_hold and not self.credit_override: | ||
message = _( | ||
"""Cannot confirm Order! The customer exceed available | ||
credit limit and is on ship hold.""" | ||
) | ||
raise ValidationError(message) | ||
else: | ||
# attempt to change the state of this order to be included in \ | ||
# the computation for check_limit function | ||
prev_state = self.state | ||
self.state = "sale" | ||
if self.partner_id.check_limit(self) and not self.credit_override: | ||
self.state = prev_state | ||
self.ship_hold = True | ||
message = _( | ||
"""Cannot confirm Order! | ||
This will exceed allowed Credit Limit. | ||
To Override, check Override Sales/Credit/Delivery Hold""" | ||
) | ||
raise ValidationError(message) | ||
self.state = prev_state | ||
return super(SaleOrder, self).action_confirm() |
Oops, something went wrong.