Skip to content

Commit

Permalink
[ADD] account_payment_term_surcharge: new module
Browse files Browse the repository at this point in the history
The new module to add surcharge in payment term and generate surcharge
invoices

closes #392

X-original-commit: 6abd1c3
Signed-off-by: Katherine Zaoral <kz@adhoc.com.ar>
Signed-off-by: Ignacio Cainelli <ica@adhoc.com.ar>
  • Loading branch information
ica-adhoc committed Mar 29, 2023
1 parent 18e9d58 commit d97db4f
Show file tree
Hide file tree
Showing 22 changed files with 589 additions and 0 deletions.
75 changes: 75 additions & 0 deletions account_payment_term_surcharge/README.rst
@@ -0,0 +1,75 @@
.. |company| replace:: ADHOC SA

.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png
:alt: ADHOC SA
:target: https://www.adhoc.com.ar

.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png

.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3

===========================
Surcharges on payment terms
===========================

This module lets us to defined a set of surcharge term to then
automatically create surcharge invoices via a scheduled action run every day.

One debit note will be created for each surcharge in the invoice which has been dues that match
with the surcharge terms.

**TODO:**

* Agregar impuestos y cuentas analiticas a las lineas de la factura de recargo


Installation
============

To install this module, you need to:

#. Only need to install the module

Configuration
=============

To configure this module, you need to:

#. In order to used please configure the surcharge product in account settings

Usage
=====

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: http://runbot.adhoc.com.ar/

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/ingadhoc/account-financial-tools/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.

Credits
=======

Images
------

* |company| |icon|

Contributors
------------

Maintainer
----------

|company_logo|

This module is maintained by the |company|.

To contribute to this module, please visit https://www.adhoc.com.ar.
2 changes: 2 additions & 0 deletions account_payment_term_surcharge/__init__.py
@@ -0,0 +1,2 @@
from . import models
from . import wizard
42 changes: 42 additions & 0 deletions account_payment_term_surcharge/__manifest__.py
@@ -0,0 +1,42 @@
##############################################################################
#
# Copyright (C) 2015 ADHOC SA (http://www.adhoc.com.ar)
# All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Surcharges on payment terms',
'version': "15.0.1.0.0",
'category': 'Accounting',
'sequence': 14,
'summary': 'Allow to add surcharges for invoices on payment terms',
'author': 'ADHOC SA',
'website': 'www.adhoc.com.ar',
'license': 'AGPL-3',
'depends': [
'account',
'account_debit_note',
],
'data': [
'views/account_payment_term_view.xml',
'views/account_payment_term_surcharge_view.xml',
'wizard/res_config_settings_views.xml',
'security/ir.model.access.csv',
'data/ir_cron_data.xml'
],
'installable': False,
'application': False,
}
15 changes: 15 additions & 0 deletions account_payment_term_surcharge/data/ir_cron_data.xml
@@ -0,0 +1,15 @@
<?xml version="1.0" ?>
<odoo noupdate="1">

<record id="cron_recurring_surcharges_invoices" model="ir.cron">
<field name="name">Create Surcharges Invoices</field>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="model_id" ref="model_account_move"/>
<field name="code">model._cron_recurring_surcharges_invoices()</field>
<field name="state">code</field>
</record>

</odoo>
4 changes: 4 additions & 0 deletions account_payment_term_surcharge/models/__init__.py
@@ -0,0 +1,4 @@
from . import account_payment_term
from . import account_payment_term_surcharge
from . import account_move
from . import res_company
97 changes: 97 additions & 0 deletions account_payment_term_surcharge/models/account_move.py
@@ -0,0 +1,97 @@
from odoo import fields, models, _
from odoo.exceptions import UserError

import logging
_logger = logging.getLogger(__name__)


class AccountMove(models.Model):
_inherit = 'account.move'

def _get_payment_term_surcharges(self):
result = []
for surcharge in self.invoice_payment_term_id.surcharge_ids:
result.append({'date': surcharge._calculate_date(self.invoice_date), 'surcharge': surcharge.surcharge})
result.sort(key=lambda x: x['date'])
return result

def _cron_recurring_surcharges_invoices(self):
_logger.info('Running Surcharges Invoices Cron Job')
self.search([
('invoice_payment_term_id.surcharge_ids', '!=', False),
('state', '=', 'posted'),
('payment_state', '=', 'not_paid')],
# buscamos facturas que tengan surcharges, esten posteadas y aun no pagadas
).create_surcharges_invoices()

def create_surcharges_invoices(self):
for rec in self:
_logger.info(
'Creating Surcharges Invoices (id: %s, company: %s)', rec.id,
rec.company_id.name)
current_date = fields.Date.context_today(self)
surcharges = rec._get_payment_term_surcharges()
for surcharge in surcharges:
if surcharge.get('date') <= current_date and surcharge.get('date') not in rec.debit_note_ids.mapped('invoice_date'):
# si tiene un surcharge el dia de hoy, se evalua que no tenga notas de debito
# con fecha de hoy, en caso de que tenga, se corre el create_invoice
rec.create_surcharge_invoice(surcharge)

def create_surcharge_invoice(self, surcharge):
self.ensure_one()
product = self.company_id.payment_term_surcharge_product_id
if not product:
raise UserError('Atención, debes configurar un producto por defecto para que aplique a la hora de crear las facturas de recargo')
debt = self.amount_residual
surcharge_percent = surcharge.get('surcharge')
to_date = surcharge.get('date')
move_debit_note_wiz = self.env['account.debit.note'].with_context(active_model="account.move",
active_ids=self.ids).create({
'date': to_date,
'reason': 'Surcharge Invoice',
})
debit_note = self.env['account.move'].browse(move_debit_note_wiz.create_debit().get('res_id'))
debit_note.narration = product.name + '.\n' + self.prepare_info(to_date, debt, surcharge.get('surcharge'))
debit_note.write({'invoice_line_ids': self._prepare_surcharge_line(product, debt, to_date, surcharge_percent)})
if self.company_id.payment_term_surcharge_invoice_auto_post:
try:
debit_note.action_post()
except Exception as exp:
_logger.error(
"Something went wrong validating "
"surcharge invoice: {}".format(exp))
raise exp

def prepare_info(self, to_date, debt, surcharge):
self.ensure_one()
# Format date to customer language
lang_code = self.env.context.get('lang', self.env.user.lang)
lang = self.env['res.lang']._lang_get(lang_code)
date_format = lang.date_format
to_date_format = to_date.strftime(date_format)
res = _(
'Deuda Vencida al %s: %s\n'
'Tasa de interés: %s') % (
to_date_format, debt, surcharge)
return res

def _prepare_surcharge_line(self, product, debt, to_date, surcharge):
self.ensure_one()
partner = self.partner_id
comment = self.prepare_info(to_date, debt, surcharge)
# fpos = partner.property_account_position_id
# taxes = product.taxes_id.filtered(
# lambda r: r.company_id == self.company_id)
# tax_id = fpos.map_tax(taxes, product)
#TODO ver si se agrega el tax manualmente o no
invoice_line_vals = [(0, 0, {
"product_id": product.id,
"quantity": 1.0,
"price_unit": (surcharge / 100) * debt,
"partner_id": partner.id,
"name": product.name + '.\n' + comment,
# "analytic_account_id": self.env.context.get('analytic_id', False),
# "tax_ids": [(6, 0, tax_id.ids)]
})]

return invoice_line_vals
11 changes: 11 additions & 0 deletions account_payment_term_surcharge/models/account_payment_term.py
@@ -0,0 +1,11 @@
from odoo import fields, models


class AccountPaymentTerm(models.Model):
_inherit = "account.payment.term"

surcharge_ids = fields.One2many(
'account.payment.term.surcharge',
'payment_term_id', string='Surcharges',
copy=True,
)
@@ -0,0 +1,62 @@
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta


class AccountPaymentTermSurcharge(models.Model):

_name = 'account.payment.term.surcharge'
_description = 'Payment Terms Surcharge'
_order = 'sequence, id'

payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms', required=True, index=True, ondelete='cascade')
surcharge = fields.Float(string="Surcharge [%]")
days = fields.Integer(string='Number of Days', required=True, default=0)
day_of_the_month = fields.Integer(string='Day of the month', help="Day of the month on which the invoice must come to its term. If zero or negative, this value will be ignored, and no specific day will be set. If greater than the last day of a month, this number will instead select the last day of this month.")
option = fields.Selection([
('day_after_invoice_date', "days after the invoice date"),
('after_invoice_month', "days after the end of the invoice month"),
('day_following_month', "of the following month"),
('day_current_month', "of the current month"),
],
default='day_after_invoice_date', required=True, string='Options'
)
sequence = fields.Integer(default=10, help="Gives the sequence order when displaying a list of payment terms lines.")

@api.constrains('surcharge')
def _check_percent(self):
for term_surcharge in self:
if (term_surcharge.surcharge < 0.0 or term_surcharge.surcharge > 100.0):
raise ValidationError(_('Percentages on the Payment Terms lines must be between 0 and 100.'))

@api.constrains('days')
def _check_days(self):
for term_surcharge in self:
if term_surcharge.option in ('day_following_month', 'day_current_month') and term_surcharge.days <= 0:
raise ValidationError(_("The day of the month used for this term must be strictly positive."))
elif term_surcharge.days < 0:
raise ValidationError(_("The number of days used for a payment term cannot be negative."))

@api.onchange('option')
def _onchange_option(self):
if self.option in ('day_current_month', 'day_following_month'):
self.days = 0

def _calculate_date(self, date_ref=None):
''' Se retorna la fecha de un recargo segun una fecha dada, esto se hace
teniendo en cuenta la configuracion propia del recargo. '''
date_ref = date_ref or fields.Date.today()
next_date = fields.Date.from_string(date_ref)
if self.option == 'day_after_invoice_date':
next_date += relativedelta(days=self.days)
if self.day_of_the_month > 0:
months_delta = (self.day_of_the_month < next_date.day) and 1 or 0
next_date += relativedelta(day=self.day_of_the_month, months=months_delta)
elif self.option == 'after_invoice_month':
next_first_date = next_date + relativedelta(day=1, months=1) # Getting 1st of next month
next_date = next_first_date + relativedelta(days=self.days - 1)
elif self.option == 'day_following_month':
next_date += relativedelta(day=self.days, months=1)
elif self.option == 'day_current_month':
next_date += relativedelta(day=self.days, months=0)
return next_date
11 changes: 11 additions & 0 deletions account_payment_term_surcharge/models/res_company.py
@@ -0,0 +1,11 @@
from odoo import api, exceptions, fields, models, _

class ResCompany(models.Model):
_inherit = 'res.company'

payment_term_surcharge_product_id = fields.Many2one(
'product.product',
'Surcharge Product',
)

payment_term_surcharge_invoice_auto_post = fields.Boolean()
3 changes: 3 additions & 0 deletions account_payment_term_surcharge/security/ir.model.access.csv
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_payment_term_surcharge,account.payment.term.surcharge,model_account_payment_term_surcharge,account.group_account_user,1,0,0,0
access_account_payment_term_surcharge_manager,account.payment.term.surcharge,model_account_payment_term_surcharge,account.group_account_manager,1,1,1,1
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_payment_term_surcharge_tree" model="ir.ui.view">
<field name="name">account.payment.term.surcharge.tree</field>
<field name="model">account.payment.term.surcharge</field>
<field name="arch" type="xml">
<tree string="Payment Term Surcharges">
<field name="sequence" widget="handle"/>
<field name="surcharge"/>
<field name="days"/>
<field name="option" string=""/>
<field name="day_of_the_month" string="Day of the month"/>
</tree>
</field>
</record>

<record id="view_payment_term_line_surcharge_form" model="ir.ui.view">
<field name="name">account.payment.term.surcharge.form</field>
<field name="model">account.payment.term.surcharge</field>
<field name="arch" type="xml">
<form string="Payment Terms Surcharges">
<field name="sequence" invisible="1"/>
<group>
<group>
<field name="surcharge" class="oe_inline"/>
</group>
</group>
<h2>Due Date Computation</h2>
<div colspan="2">
<label for="days" string="Due" attrs="{'invisible': [('option','!=', 'day_after_invoice_date')]}"/>
<label for="days" string="Due the" attrs="{'invisible': [('option','=', 'day_after_invoice_date')]}"/>
<field name="days" class="oe_inline"/>
<label for="option" string=""/> <!--Empty label to force space between elements-->
<field name="option" class="oe_inline"/>
</div>
<div colspan="2" attrs="{'invisible': [('option','!=', 'day_after_invoice_date')]}">
<label for="day_of_the_month" string="On the"/>
<field name="day_of_the_month" class="oe_inline"/>
<span class="o_form_label">of the month</span>
</div>
</form>
</field>
</record>
</data>
</odoo>



16 changes: 16 additions & 0 deletions account_payment_term_surcharge/views/account_payment_term_view.xml
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_payment_term_form" model="ir.ui.view">
<field name="name">account.payment.term.form</field>
<field name="model">account.payment.term</field>
<field name="inherit_id" ref="account.view_payment_term_form"/>
<field name="arch" type="xml">
<field name="line_ids" position="after">
<separator string="Surcharges"/>
<field name="surcharge_ids"/>
</field>
</field>
</record>
</data>
</odoo>

0 comments on commit d97db4f

Please sign in to comment.