move account_credit_hold into bemade-addons

This commit is contained in:
Marc Durepos 2023-12-18 15:20:33 -05:00
parent 6ff1bf5706
commit 148fc500ed
16 changed files with 337 additions and 0 deletions

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,30 @@
{
'name': 'Account Credit Hold',
'version': '15.0.2.0.0.1',
'summary': 'Allows setting clients on credit hold, blocking the ability confirm a new sales order.',
'description': 'Allows setting clients on hold, blocking the ability confirm a new sales order.',
'category': 'Accounting/Accounting',
'author': 'Bemade Inc.',
'maintainer': 'Marc Durepos <marc@bemade.org>',
'website': 'http://www.bemade.org',
'license': 'LGPL-3',
'depends': ['sale', 'account_followup', 'stock'],
'data': [
'views/account_followup_views.xml',
'views/sale_order_views.xml',
'views/res_partner_views.xml',
'views/stock_picking_views.xml',
],
'assets': {
'web.assets_backend': [
'account_credit_hold/static/src/js/followup_form_model.js',
'account_credit_hold/static/src/js/followup_form_controller.js',
],
'web.assets_qweb': [
'account_credit_hold/static/src/xml/account_followup_template.xml',
],
},
'demo': [],
'installable': True,
'auto_install': False
}

View file

@ -0,0 +1,5 @@
from . import account_followup
from . import res_partner
from . import sale_order
from . import stock_picking
from . import account_followup_report

View file

@ -0,0 +1,8 @@
from odoo import fields, models, api
class FollowupLine(models.Model):
_inherit = 'account_followup.followup.line'
account_hold = fields.Boolean(string="Place on Credit Hold",
help="Place clients on account hold, restricting confirmation of new orders.")

View file

@ -0,0 +1,14 @@
from odoo import models, fields, api, _
class FollowUpReport(models.AbstractModel):
_inherit = 'account.followup.report'
def _get_line_info(self, followup_line):
res = super()._get_line_info(followup_line)
res.update({'credit_hold': followup_line.account_hold})
return res
@api.model
def credit_hold(self, options):
partner_id = options['partner_id']
partner = self.env['res.partner'].browse(partner_id)
partner.action_credit_hold()

View file

@ -0,0 +1,59 @@
from odoo import fields, models, api, _
from datetime import date
class Partner(models.Model):
_inherit = 'res.partner'
postpone_hold_until = fields.Date(string="Postpone Hold",
help="Grace period specific to this partner despite unpaid invoices.", )
hold_bg = fields.Boolean(string="Hold (technical)",
compute="_compute_hold_bg",
store=True,
default=False)
on_hold = fields.Boolean(string="Account on Hold",
help="Client account is on hold for unpaid overdue invoices.",
compute="_compute_on_hold")
@api.depends('postpone_hold_until', 'hold_bg')
def _compute_on_hold(self):
# manually re-compute hold_bg since followup_status doesn't get updated in Python but gets recalculated
# by an SQL query every time
self._compute_hold_bg()
for rec in self:
if rec.hold_bg and not (rec.postpone_hold_until and rec.postpone_hold_until > date.today()):
rec.on_hold = True
else:
rec.on_hold = False
@api.autovacuum
def _cleanup_expired_hold_postponements(self):
expired_holds = self.search([('postpone_hold_until', '<=', date.today())])
expired_holds.write({'postpone_hold_until': False})
def action_credit_hold(self):
message = _('Placed on credit hold')
for rec in self:
rec.hold_bg = True
rec.message_post()
def _execute_followup_partner(self):
res = super()._execute_followup_partner()
if self.followup_status == 'in_need_of_action':
if self.followup_line_id.account_hold:
self.action_credit_hold()
return res
# BV: FOR MIGRATION
#@api.depends('followup_status', 'followup_level')
def _compute_hold_bg(self):
first_followup_level = self.env['account_followup.followup.line'].search(
[('company_id', '=', self.env.company.id)], order="delay asc", limit=1)
for rec in self:
prev_hold_bg = rec.hold_bg
level = rec.followup_line_id
if rec.followup_status == 'no_action_needed' and level == first_followup_level:
rec.hold_bg = False
else:
rec.hold_bg = prev_hold_bg

View file

@ -0,0 +1,17 @@
from odoo import fields, models, api, _
from odoo.exceptions import UserError
class SaleOrder(models.Model):
_inherit = "sale.order"
client_on_hold = fields.Boolean(string='Client on Hold',
help="Whether or not a client has been put on hold due to unpaid invoices.",
related="partner_id.on_hold")
@api.depends('client_on_hold')
def action_confirm(self):
if any(self.mapped('client_on_hold')):
raise UserError(_("This client is on credit hold. No new orders can be confirmed until past-due invoices "
"are paid or the accounting team postpones the hold."))
super().action_confirm()

View file

@ -0,0 +1,9 @@
from odoo import fields, models, api
class ModelName(models.Model):
_inherit = "stock.picking"
client_on_hold = fields.Boolean(string='Client on Hold',
help="Whether or not a client has been put on hold due to unpaid invoices.",
related="partner_id.on_hold")

View file

@ -0,0 +1,35 @@
# Overview
This module adds the notion of placing clients on credit hold to the followup levels from the Odoo Enterprise
account_followup module. It adds an option to followup levels to mark clients matching the followup criteria as on
credit hold. This hold restricts the confirmation of new sales orders for these clients.
Accountant and admin users can set a date until which the account hold will be
postponed on a specific partner's form view. This effectively gives clients an extra
grace period, allowing orders to be confirmed until the period ends.
# Change Log
## 15.0.2.0.0 (2023-05-04)
Complete remake of the module, making the "Credit Hold" an action that is either manually or
automatically triggered from the Accounting > Followup Reports section or by setting the automatic application field
on followup levels.
## 15.0.1.1.0 (2023-05-03)
Adds a ribbon to stock pickings for clients on hold, and therefore a dependency on stock.
## 15.0.1.0.2 (2023-05-03)
Fix to sale order view and sale order confirmation for clients not on hold.
## 15.0.1.0.1 (2023-05-03)
Fix clients on hold when status is "outstanding_invoices".
## 15.0.1.0.0 (2023-05-02) Initial Release
Initial release of the module, including a setting on follow-up levels to toggle placing on credit hold. Blocks
the confirmation of sales orders for clients on credit hold. Red "Credit Hold" banner appears on sales orders and
partner form view when a client is on credit hold. Credit hold can be postponed by setting the "Postpone Hold" field
on the partner form view.

View file

@ -0,0 +1,48 @@
/** @odoo-module **/
var FollowupFormController = require('account_followup.FollowupFormController');
import { patch } from '@web/core/utils/patch';
var PatchedController = patch(FollowupFormController.prototype, "followup_form_controller", {
events: _.extend({}, FollowupFormController.prototype.events, {
'click .o_account_followup_credit_hold_button': '_onCreditHold',
}),
updateButtons() {
this._super(...arguments);
let setButtonClass = (button, primary) => {
/* Set class 'btn-primary' if parameter `primary` is true
* 'btn-secondary' otherwise
*/
let addedClass = primary ? 'btn-primary' : 'btn-secondary'
let removedClass = !primary ? 'btn-secondary' : 'btn-primary'
this.$buttons.find(`button.${button}`)
.removeClass(removedClass).addClass(addedClass);
}
if (!this.$buttons) {
return;
}
let followupLevel = this.model.localData[this.handle].data.followup_level;
setButtonClass('o_account_followup_credit_hold_button', followupLevel.credit_hold);
},
_onCreditHold: function() {
var self = this;
this.model.doCreditHold(this.handle);
this.options = {
partner_id: this._getPartner()
};
this._rpc({
model: 'account.followup.report',
method: 'credit_hold',
args: [this.options],
}).then(function (result) {
self._removeHighlightCreditHold();
self._displayDone();
});
},
_removeHighlightCreditHold: function() {
this.$buttons.find('button.o_account_followup_credit_hold_button')
.removeClass('btn-primary').addClass('btn-secondary');
},
});
export { PatchedController };

View file

@ -0,0 +1,14 @@
/** @odoo-module **/
var FollowupFormModel = require('account_followup.FollowupFormModel');
import { patch } from '@web/core/utils/patch';
var PatchedModel = patch(FollowupFormModel.prototype, 'followup_form_model', {
doCreditHold: function(handle) {
var level = this.localData[handle].data.followup_level;
if(level && level.credit_hold) {
level.credit_hold = false;
}
},
});
export { PatchedModel };

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-inherit="account_followup.CustomerStatements.buttons" t-inherit-mode="extension">
<xpath expr="//button[hasclass('o_account_followup_print_letter_button')]" position="before">
<button type="button" class="btn btn-primary o_account_followup_credit_hold_button">
Credit Hold
</button>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="account_followup_followup_line_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.account_followup_line.form</field>
<field name="model">account_followup.followup.line</field>
<field name="inherit_id" ref="account_followup.view_account_followup_followup_line_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='send_email']" position="before">
<field name="account_hold" />
</xpath>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="res_partner_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.res_partner.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="before">
<field name="hold_bg" invisible="True" />
<field name="on_hold" invisible="True" />
<widget name="web_ribbon" title="Credit Hold"
bg_color="bg-danger" attrs="{'invisible': [('on_hold','=',False)]}"/>
</xpath>
</field>
</record>
<record id="view_partner_property_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.view_partner_property_form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='banks']" position="before">
<group string="Credit Hold" >
<field name="postpone_hold_until"
groups="account.group_account_manager,account.group_account_user"
attrs="{'readonly': [('hold_bg','=',False), ('postpone_hold_until','=',False)]}"/>
</group>
</xpath>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="sale_order_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.sale_order.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="before">
<field name="client_on_hold" invisible="True" />
<widget name="web_ribbon" title="Credit Hold"
bg_color="bg-danger" attrs="{'invisible': [('client_on_hold','=',False)] }"/>
</xpath>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="stock_picking_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.stock_picking.form</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="priority" eval="8"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="before">
<field name="client_on_hold" invisible="True" />
<widget name="web_ribbon" title="Credit Hold"
bg_color="bg-danger" attrs="{'invisible': [('client_on_hold','=',False)] }"/>
</xpath>
</field>
</record>
</data>
</odoo>