[IMP] account: Added feature to create recurrent vendor bills,

- One need to first enable this from Accounting Settings to use this feature
- This feature works for Vendor Bills which are validated/paid
- Once activated, user can see one option in Vendor Bills to set particular record to be recurrent document and can define the recurrency period. This will automatically create draft documents(by mean of Scheduled Actions) as per recurrency configuration.

Was PR #21411. was task 34884.
This commit is contained in:
Sanjay Jamod
2018-01-26 16:56:57 +01:00
committed by qdp-odoo
parent a094095ef3
commit 7642754122
9 changed files with 216 additions and 4 deletions
+1
View File
@@ -17,6 +17,7 @@ Core mechanisms for the accounting modules. To display the menuitems, install th
'security/ir.model.access.csv',
'data/data_account_type.xml',
'data/account_data.xml',
'data/ir_cron_data.xml',
'views/account_menuitem.xml',
'views/account_payment_view.xml',
'wizard/account_reconcile_view.xml',
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- Cron job for creating recurring vendor bills -->
<record id="recurrent_vendor_bills_cron" model="ir.cron">
<field name="name">Account: generate recurring vendor bills</field>
<field name="model_id" ref="account.model_account_invoice"/>
<field name="state">code</field>
<field name="active">False</field>
<field name="code">model._cron_generate_recurring_vendor_bill()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
</record>
</odoo>
+64
View File
@@ -229,6 +229,12 @@ class AccountInvoice(models.Model):
payment_lines.update(line.mapped('matched_debit_ids.debit_move_id.id'))
self.payment_move_line_ids = self.env['account.move.line'].browse(list(payment_lines))
@api.depends('date_invoice', 'is_recurrency_enabled', 'recurrency_interval', 'recurrency_type')
def _compute_next_recurrency_date(self):
#initially set the recurrency_date
for invoice in self.filtered(lambda inv: inv.is_recurrency_enabled and inv.recurrency_interval and inv.recurrency_type and inv.date_invoice and not inv.next_recurrency_date):
invoice.next_recurrency_date = invoice._get_next_recurrency_date(invoice.date_invoice)
name = fields.Char(string='Reference/Description', index=True,
readonly=True, states={'draft': [('readonly', False)]}, copy=False, help='The name that will be used on account move lines')
@@ -367,6 +373,18 @@ class AccountInvoice(models.Model):
#fields use to set the sequence, on the first invoice of the journal
sequence_number_next = fields.Char(string='Next Number', compute="_get_sequence_number_next", inverse="_set_sequence_next")
sequence_number_next_prefix = fields.Char(string='Next Number Prefix', compute="_get_sequence_prefix")
#recurrent vendor bills fields
is_recurrency_enabled = fields.Boolean(string="Generate Recurring Document(s)", copy=False, help="Tick this box to generate recurring bills. The recurring bills will only be generated if this bill is validated.")
recurrency_interval = fields.Integer(string='Every', default=1, copy=False, help="Define the recurrency of the vendor bill.")
recurrency_type = fields.Selection([
('days', 'Day(s)'),
('weeks', 'Week(s)'),
('months', 'Month(s)'),
('years', 'Year(s)'),
], string="Recurrency Type", default='months', copy=False)
# Technical field depicting whether the document is generated by cron or by user
is_recurring_document = fields.Boolean(string="Recurring Document", copy=False)
next_recurrency_date = fields.Date(compute=_compute_next_recurrency_date, _string='Next Recurrency Date', copy=False, store=True)
_sql_constraints = [
('number_uniq', 'unique(number, company_id, journal_id, type)', 'Invoice Number must be unique per Company!'),
@@ -450,6 +468,52 @@ class AccountInvoice(models.Model):
self.type == 'in_refund' and self.state == 'draft' and _('Vendor Credit Note') or \
self.type == 'in_refund' and _('Vendor Credit Note - %s') % (self.number)
def _get_next_recurrency_date(self, current_date):
"""
This method is used to return next date based on recurrency configuration of the record.
:param current_date: Origin date from which next date is to be generated.
:returns: future date on which a duplicated document will be generated by crons.
"""
assert self.recurrency_type, "Invalid `Recurrency` interval! Please set it to either of these: 'Day(s)', 'Week(s)', 'Month(s)' or 'Year(s)'."
self.ensure_one()
recurring_next_date = fields.Date.from_string(current_date) + relativedelta(**{self.recurrency_type: self.recurrency_interval})
# In case of monthly recurrent invoice, future invoices should always be created on same day as of it's main invoice but
# if the reference invoice was made on last day of the month, then the recurring invoice should be created on last day of next month.
# example, main invoice is set to 31st Jan, the next invoices should be created on 28/29th Feb, 31st Mar, 30 Apr and so on
if self.recurrency_type == 'months':
reference_invoice_day = fields.Date.from_string(self.date_invoice).day
recurring_next_date += relativedelta(day=reference_invoice_day)
if reference_invoice_day == fields.Date.from_string(self.date_invoice) + relativedelta(day=31):
recurring_next_date += relativedelta(day=31)
return recurring_next_date
@api.model
def _cron_generate_recurring_vendor_bill(self):
today = fields.Date.today()
domain = [
('is_recurrency_enabled', '=', True),
('state', 'not in', ['draft', 'cancel']),
('next_recurrency_date', '<=', today)
]
for invoice in self.search(domain):
while invoice.next_recurrency_date <= today:
invoice._create_recurrent_invoice()
invoice.next_recurrency_date = invoice._get_next_recurrency_date(invoice.next_recurrency_date)
def _create_recurrent_invoice(self):
self.ensure_one()
invoice = self.copy({
'origin': '',
'date_invoice': self.next_recurrency_date,
'is_recurring_document': True,
})
# set the due date
#invoice._onchange_payment_term_date_invoice()
link = '<a href=# data-oe-model=account.invoice data-oe-id=%d>%s</a>' % (self.id, self.number)
invoice.message_post(body=_('This bill has been generated by %s') % link)
return invoice
@api.model
def create(self, vals):
onchanges = {
@@ -56,10 +56,12 @@ class ResConfigSettings(models.TransientModel):
tax_exigibility = fields.Boolean(string='Cash Basis', related='company_id.tax_exigibility')
tax_cash_basis_journal_id = fields.Many2one('account.journal', related='company_id.tax_cash_basis_journal_id', string="Tax Cash Basis Journal")
account_hide_setup_bar = fields.Boolean(string='Hide Setup Bar', related='company_id.account_setup_bar_closed',help="Tick if you wish to hide the setup bar on the dashboard")
group_allow_recurring = fields.Boolean(string="Recurring Vendor Bills", implied_group="account.group_allow_recurring")
@api.multi
def set_values(self):
super(ResConfigSettings, self).set_values()
self.env.ref('account.recurrent_vendor_bills_cron').write({'active': self.group_allow_recurring})
if self.group_multi_currency:
self.env.ref('base.group_user').write({'implied_ids': [(4, self.env.ref('product.group_sale_pricelist').id)]})
""" install a chart of accounts for the given company (if required) """
@@ -32,6 +32,11 @@
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_allow_recurring" model="res.groups">
<field name="name">Allow to create recurring vendor bills</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_cash_rounding" model="res.groups">
<field name="name">Allow the cash rounding management</field>
<field name="category_id" ref="base.module_category_hidden"/>
+1
View File
@@ -6,6 +6,7 @@ from . import test_account_move_closed_period
from . import test_account_supplier_invoice
from . import test_account_validate_account_move
from . import test_account_invoice_rounding
from . import test_account_supplier_invoice_recurrent
from . import test_bank_statement_reconciliation
#TODO re-enableand fix this test
#from . import test_bank_stmt_reconciliation_widget_ui
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestAccountRecurrent(AccountingTestCase):
def apply_vendor_cron(self):
self.env.ref('account.recurrent_vendor_bills_cron').method_direct_trigger()
def test_create_recurring_vendor_bills(self):
AccountInvoice = self.env['account.invoice']
invoice_account_id = self.env['account.account'].search([('user_type_id', '=', self.ref('account.data_account_type_receivable'))], limit=1).id
invoice_line_account_id = self.env['account.account'].search([('user_type_id', '=', self.ref('account.data_account_type_expenses'))], limit=1).id
purchase_journal = self.env['account.journal'].search([('type', '=', 'purchase')], limit=1)
invoice_line_data = [
(0, 0,
{
'product_id': self.ref('product.product_product_4'),
'name': 'product 4 that cost 100',
'quantity': 1.0,
'price_unit': 100.0,
'account_id': invoice_line_account_id,
}
),
(0, 0,
{
'product_id': self.ref('product.product_product_5'),
'name': 'product 5 that cost 200',
'quantity': 2.0,
'price_unit': 200.0,
'account_id': invoice_line_account_id,
}
)
]
# Create an invoice dated 2 months before today
invoice = AccountInvoice.create({
'partner_id': self.ref('base.res_partner_2'),
'account_id': invoice_account_id,
'name': 'invoice test recurrent',
'type': 'in_invoice',
'reference_type': 'none',
'date_invoice': datetime.today() + relativedelta(months=-2),
'journal_id': purchase_journal.id,
'is_recurrency_enabled': True,
'recurrency_interval': 1,
'recurrency_type': 'months',
'invoice_line_ids': invoice_line_data
})
recurring_domain = [('type', '=', 'in_invoice'), ('state', '=', 'draft'), ('is_recurring_document', '=', True)]
# After executing cron, verify that no bill is auto generated if the recurrent bill is in `draft` state
self.apply_vendor_cron()
recurring_invoice_count = AccountInvoice.search_count(recurring_domain)
self.assertEquals(recurring_invoice_count, 0, 'Recurring bills should not be generated when reference bill is still `draft`.')
# After validating bill, run cron and check that 2 recurring bill have been generated
invoice.action_invoice_open() # Validate invoice
self.apply_vendor_cron()
recurring_invoice_count = previous_recurring_invoice_count = AccountInvoice.search_count(recurring_domain)
self.assertEquals(recurring_invoice_count, 2, '2 recurring bills should be generated.')
# verify that bills aren't generated if reference bill is in `cancel` state
purchase_journal.write({'update_posted': True}) # Allow to cancel the invoice
inv2_data = {
'name': 'invoice test recurrent 2',
'date_invoice': datetime.today() + relativedelta(months=-2),
'is_recurrency_enabled': True,
'recurrency_interval': 1,
'recurrency_type': 'months',
}
invoice2 = invoice.copy(default=inv2_data)
invoice2.action_invoice_cancel() # Cancel the invoice
self.apply_vendor_cron()
recurring_invoice_count = AccountInvoice.search_count(recurring_domain)
self.assertEquals(previous_recurring_invoice_count, recurring_invoice_count, 'Recurring bills should not be generated when reference bill is in `cancel` state.')
# verify that vendor bills are generated even when invoice is in `paid` state
inv3_data = {
'name': 'invoice test recurrent 3',
'date_invoice': datetime.today() + relativedelta(months=-2),
'is_recurrency_enabled': True,
'recurrency_interval': 1,
'recurrency_type': 'months',
}
invoice3 = invoice.copy(default=inv3_data)
invoice3.action_invoice_open() # Validate invoice
Payment = self.env['account.payment']
payment_vals = Payment.with_context(default_invoice_ids=[(4, invoice3.id, None)]).default_get(Payment._fields.keys())
payment_vals.update({
'payment_method_id': self.ref("account.account_payment_method_manual_out"),
'journal_id': self.env['account.journal'].search([('type', '=', 'bank')], limit=1).id
})
Payment.create(payment_vals).action_validate_invoice_payment() # Register the payment
self.apply_vendor_cron()
recurring_invoice_count = AccountInvoice.search_count(recurring_domain)
self.assertEquals(recurring_invoice_count, 4, '4 recurring bills should be generated.')
@@ -334,6 +334,12 @@
<field name="move_id" groups="account.group_account_user" attrs="{'invisible': [('move_id', '=', False)]}"/>
<field name="date" domain="[('state', '=', 'draft'), ('company_id', '=', company_id)]" groups="account.group_account_user"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<field name="is_recurrency_enabled" string="Recurring Bills" help="Tick this box to generate recurring bills. The recurring bills will only be generated if this bill is validated." groups="account.group_allow_recurring" attrs="{'invisible': [('type', '!=', 'in_invoice')]}"/>
<label for="recurrency_interval" attrs="{'invisible': [('is_recurrency_enabled', '=', False)]}" groups="account.group_allow_recurring"/>
<div class="o_row" attrs="{'invisible': [('is_recurrency_enabled', '=', False)]}" groups="account.group_allow_recurring">
<field name="recurrency_interval" help="Define the recurrency of the vendor bill." attrs="{'required': [('is_recurrency_enabled', '=', True)]}"/>
<field name="recurrency_type" attrs="{'required': [('is_recurrency_enabled', '=', True)]}"/>
</div>
</group>
</group>
</page>
@@ -574,6 +580,10 @@
<xpath expr="filter[@name='invoices']" position="attributes">
<attribute name="string">Not Draft</attribute>
</xpath>
<xpath expr="filter[@name='myinvoices']" position="after">
<filter domain="[('is_recurrency_enabled','=',True)]" name="is_recurrency_enabled" help="Bills Generating Recurring Bills" groups="account.group_allow_recurring" invisible="context.get('type') != 'in_invoice'"/>
<filter domain="[('is_recurring_document','=',True)]" name="is_recurring_document" help="Bills Generated Automatically" groups="account.group_allow_recurring" invisible="context.get('type') != 'in_invoice'"/>
</xpath>
<filter name="refunds" position="replace"/>
</data>
</field>
@@ -434,9 +434,20 @@
</div>
</div>
</div>
<h2 groups="account.group_account_user">Automated Entries</h2>
<div class="row mt16 o_settings_container" id="recommended_apps" groups="account.group_account_user">
<div class="col-xs-12 col-md-6 o_setting_box" id="asset_types" title="This allows you to manage the assets owned by a company or a person. It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.">
<h2>Automated Entries</h2>
<div class="row mt16 o_settings_container" id="recommended_apps">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="group_allow_recurring"/>
</div>
<div class="o_setting_right_pane">
<label for="group_allow_recurring"/>
<div class="text-muted">
Create recurring vendor bills
</div>
</div>
</div>
<div groups="account.group_account_user" class="col-xs-12 col-md-6 o_setting_box" id="asset_types" title="This allows you to manage the assets owned by a company or a person. It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.">
<div class="o_setting_left_pane">
<field name="module_account_asset"/>
</div>
@@ -452,7 +463,7 @@
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box" title="This allows you to manage the revenue recognition on selling products. It keeps track of the installments occurred on those revenue recognitions, and creates account moves for those installment lines.">
<div groups="account.group_account_user" class="col-xs-12 col-md-6 o_setting_box" title="This allows you to manage the revenue recognition on selling products. It keeps track of the installments occurred on those revenue recognitions, and creates account moves for those installment lines.">
<div class="o_setting_left_pane">
<field name="module_account_deferred_revenue" widget="upgrade_boolean"/>
</div>