diff --git a/round_off_invoices/__init__.py b/round_off_invoices/__init__.py new file mode 100644 index 000000000..2b06ca5f5 --- /dev/null +++ b/round_off_invoices/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Treesa Maria Jude() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +import models diff --git a/round_off_invoices/__manifest__.py b/round_off_invoices/__manifest__.py new file mode 100644 index 000000000..0eab50565 --- /dev/null +++ b/round_off_invoices/__manifest__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Treesa Maria Jude() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +{ + 'name': 'Round Off Invoice Amount', + 'version': '10.0.1.0.0', + 'summary': 'Allows Rounding on Invoice Amount', + 'description': 'Invoice amount is rounded to their nearest digits excluding the decimal part', + 'category': 'Accounting', + 'author': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'depends': ['base', 'account'], + 'data': [ + 'views/round_off_view.xml', + ], + 'license': 'AGPL-3', + 'images': ['static/description/banner.jpg'], + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/round_off_invoices/models/__init__.py b/round_off_invoices/models/__init__.py new file mode 100644 index 000000000..5f67f7a6d --- /dev/null +++ b/round_off_invoices/models/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Treesa Maria Jude() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +import config diff --git a/round_off_invoices/models/config.py b/round_off_invoices/models/config.py new file mode 100644 index 000000000..cc8c77726 --- /dev/null +++ b/round_off_invoices/models/config.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Treesa Maria Jude() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + + +from odoo.tools import float_is_zero +from odoo.exceptions import UserError +from odoo import fields, models, api, _ + + +class RoundOffSetting(models.TransientModel): + _inherit = 'account.config.settings' + + round_off = fields.Boolean(string='Allow rounding of invoice amount', help="Allow rounding of invoice amount") + round_off_account = fields.Many2one('account.account', string='Round Off Account') + + @api.multi + def set_round_off(self): + ir_values_obj = self.env['ir.values'] + ir_values_obj.sudo().set_default('account.config.settings', "round_off", self.round_off) + ir_values_obj.sudo().set_default('account.config.settings', "round_off_account", self.round_off_account.id) + + +class AccountRoundOff(models.Model): + _inherit = 'account.invoice' + + round_off_value = fields.Float(compute='_compute_amount', string='Round off amount') + rounded_total = fields.Float(compute='_compute_amount', string='Rounded Total') + round_active = fields.Boolean(compute='get_round_active') + + def get_round_active(self): + ir_values = self.env['ir.values'] + self.round_active = ir_values.get_default('account.config.settings', 'round_off') + + @api.one + @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'currency_id', 'company_id', + 'date_invoice', 'type') + def _compute_amount(self): + self.amount_untaxed = sum(line.price_subtotal for line in self.invoice_line_ids) + self.amount_tax = sum(line.amount for line in self.tax_line_ids) + self.rounded_total = round(self.amount_untaxed + self.amount_tax) + self.amount_total = self.amount_untaxed + self.amount_tax + self.round_off_value = self.rounded_total - (self.amount_untaxed + self.amount_tax) + amount_total_company_signed = self.amount_total + amount_untaxed_signed = self.amount_untaxed + if self.currency_id and self.company_id and self.currency_id != self.company_id.currency_id: + currency_id = self.currency_id.with_context(date=self.date_invoice) + amount_total_company_signed = currency_id.compute(self.amount_total, self.company_id.currency_id) + amount_untaxed_signed = currency_id.compute(self.amount_untaxed, self.company_id.currency_id) + sign = self.type in ['in_refund', 'out_refund'] and -1 or 1 + self.amount_total_company_signed = amount_total_company_signed * sign + self.amount_total_signed = self.amount_total * sign + self.amount_untaxed_signed = amount_untaxed_signed * sign + + @api.one + @api.depends( + 'state', 'currency_id', 'invoice_line_ids.price_subtotal', + 'move_id.line_ids.amount_residual', + 'move_id.line_ids.currency_id') + def _compute_residual(self): + residual = 0.0 + residual_company_signed = 0.0 + sign = self.type in ['in_refund', 'out_refund'] and -1 or 1 + for line in self.sudo().move_id.line_ids: + if line.account_id.internal_type in ('receivable', 'payable'): + residual_company_signed += line.amount_residual + if line.currency_id == self.currency_id: + residual += line.amount_residual_currency if line.currency_id else line.amount_residual + else: + from_currency = (line.currency_id and line.currency_id.with_context( + date=line.date)) or line.company_id.currency_id.with_context(date=line.date) + residual += from_currency.compute(line.amount_residual, self.currency_id) + self.residual_company_signed = abs(residual_company_signed) * sign + self.residual_signed = abs(residual) * sign + if self.round_active is True and self.type == 'out_invoice': + self.residual = round(abs(residual)) + else: + self.residual = abs(residual) + digits_rounding_precision = self.currency_id.rounding + if float_is_zero(self.residual, precision_rounding=digits_rounding_precision): + self.reconciled = True + else: + self.reconciled = False + + @api.multi + def action_move_create(self): + """ Creates invoice related analytics and financial move lines """ + account_move = self.env['account.move'] + + for inv in self: + + if not inv.journal_id.sequence_id: + raise UserError(_('Please define sequence on the journal related to this invoice.')) + if not inv.invoice_line_ids: + raise UserError(_('Please create some invoice lines.')) + if inv.move_id: + continue + + ctx = dict(self._context, lang=inv.partner_id.lang) + + if not inv.date_invoice: + inv.with_context(ctx).write({'date_invoice': fields.Date.context_today(self)}) + date_invoice = inv.date_invoice + company_currency = inv.company_id.currency_id + + # create move lines (one per invoice line + eventual taxes and analytic lines) + iml = inv.invoice_line_move_line_get() + iml += inv.tax_line_move_line_get() + + diff_currency = inv.currency_id != company_currency + # create one move line for the total and possibly adjust the other lines amount + total, total_currency, iml = inv.with_context(ctx).compute_invoice_totals(company_currency, iml) + + name = inv.name or '/' + if inv.payment_term_id: + totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute( + total, date_invoice)[0] + res_amount_currency = total_currency + ctx['date'] = date_invoice + for i, t in enumerate(totlines): + if inv.currency_id != company_currency: + amount_currency = company_currency.with_context(ctx).compute(t[1], inv.currency_id) + else: + amount_currency = False + + # last line: add the diff + res_amount_currency -= amount_currency or 0 + if i + 1 == len(totlines): + amount_currency += res_amount_currency + if self.round_active is True and self.type == 'out_invoice': + iml.append({ + 'type': 'dest', + 'name': name, + 'price': t[1]+self.round_off_value, + 'account_id': inv.account_id.id, + 'date_maturity': t[0], + 'amount_currency': diff_currency and amount_currency, + 'currency_id': diff_currency and inv.currency_id.id, + 'invoice_id': inv.id + }) + ir_values = self.env['ir.values'] + acc_id = ir_values.get_default('account.config.settings', 'round_off_account') + iml.append({ + 'type': 'dest', + 'name': "Round off", + 'price': -self.round_off_value, + 'account_id': acc_id, + 'date_maturity': t[0], + 'amount_currency': diff_currency and amount_currency, + 'currency_id': diff_currency and inv.currency_id.id, + 'invoice_id': inv.id + }) + else: + + iml.append({ + 'type': 'dest', + 'name': name, + 'price': t[1], + 'account_id': inv.account_id.id, + 'date_maturity': t[0], + 'amount_currency': diff_currency and amount_currency, + 'currency_id': diff_currency and inv.currency_id.id, + 'invoice_id': inv.id + }) + + else: + if self.round_active is True and self.type == 'out_invoice': + iml.append({ + 'type': 'dest', + 'name': name, + 'price': total + self.round_off_value, + 'account_id': inv.account_id.id, + 'date_maturity': inv.date_due, + 'amount_currency': diff_currency and amount_currency, + 'currency_id': diff_currency and inv.currency_id.id, + 'invoice_id': inv.id + }) + ir_values = self.env['ir.values'] + acc_id = ir_values.get_default('account.config.settings', 'round_off_account') + iml.append({ + 'type': 'dest', + 'name': "Round off", + 'price': -self.round_off_value, + 'account_id': acc_id, + 'date_maturity': inv.date_due, + 'amount_currency': diff_currency and amount_currency, + 'currency_id': diff_currency and inv.currency_id.id, + 'invoice_id': inv.id + }) + else: + iml.append({ + 'type': 'dest', + 'name': name, + 'price': total, + 'account_id': inv.account_id.id, + 'date_maturity': inv.date_due, + 'amount_currency': diff_currency and total_currency, + 'currency_id': diff_currency and inv.currency_id.id, + 'invoice_id': inv.id + }) + part = self.env['res.partner']._find_accounting_partner(inv.partner_id) + line = [(0, 0, self.line_get_convert(l, part.id)) for l in iml] + line = inv.group_lines(iml, line) + + journal = inv.journal_id.with_context(ctx) + line = inv.finalize_invoice_move_lines(line) + + date = inv.date or date_invoice + move_vals = { + 'ref': inv.reference, + 'line_ids': line, + 'journal_id': journal.id, + 'date': date, + 'narration': inv.comment, + } + ctx['company_id'] = inv.company_id.id + ctx['invoice'] = inv + ctx_nolang = ctx.copy() + ctx_nolang.pop('lang', None) + move = account_move.with_context(ctx_nolang).create(move_vals) + # Pass invoice in context in method post: used if you want to get the same + # account move reference when creating the same invoice after a cancelled one: + move.post() + # make the invoice point to that move + vals = { + 'move_id': move.id, + 'date': date, + 'move_name': move.name, + } + inv.with_context(ctx).write(vals) + return True diff --git a/round_off_invoices/static/description/banner.jpg b/round_off_invoices/static/description/banner.jpg new file mode 100644 index 000000000..dc1bd02a6 Binary files /dev/null and b/round_off_invoices/static/description/banner.jpg differ diff --git a/round_off_invoices/static/description/cybro_logo.png b/round_off_invoices/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/round_off_invoices/static/description/cybro_logo.png differ diff --git a/round_off_invoices/static/description/icon.png b/round_off_invoices/static/description/icon.png new file mode 100644 index 000000000..e84648232 Binary files /dev/null and b/round_off_invoices/static/description/icon.png differ diff --git a/round_off_invoices/static/description/index.html b/round_off_invoices/static/description/index.html new file mode 100644 index 000000000..8c60c4e0c --- /dev/null +++ b/round_off_invoices/static/description/index.html @@ -0,0 +1,138 @@ +
+
+

Round Off Invoice Amount

+

Rounding on Invoice Amount

+

Cybrosys Technologies

+ +
+
+ +
+
+
+

Overview

+

+ In any business, rounding of invoice amount is a necessary feature. By default, Odoo ERP +doesn't support this feature. But the 'Round off Invoice Amount' app from Cybrosys allows +you to round off the invoice amount to the nearest whole amount by excluding the decimal +parts. This application also supports you to create separate journal entries for the rounded +amounts, which can assure the amount is balanced. +

+
+
+
+ +
+
+

Allow Round Off Feature

+ +
+
+

First, you have to download and Install the application form Odoo App Store. Then follow +the below steps to enable the feature.

+

Accounting -->Settings -->Round Off

+

    +
  •    Tick the 'Allow rounding of invoice amount' option.
  • +
  •    Don't forget to add the Round off Account/ +(Journal entry corresponding to write off amount will be passed to this account)
  • +
+

+
+ +
+
+
+
+
+ +
+
+

Round Off Amounts

+
+
+ +

Goto

+

Accounting -->Customer Invoices -->Create

+ +
+ +
+
+
+
+
+ +
+
+

Validate the Invoice

+
+
+

Here, the 'Total amount' is the actual amount. 'Amount Due' is + the amount after rounding off the decimal parts and it is the one customer needs to pay. +

+
+ +
+
+
+
+
+ +
+
+

Journal Entries

+
+
+

Goto

+

Accounting -->Customer Invoices -->Invoice -->Other Info -->Journal Entry

+

As you can see in the picture a special journal entry is passed to balance the amount. +

+
+ +
+
+
+
+
+ +
+
+

Round Off Details

+
+
+

Goto

+

Accounting -->Journal Items-->Search: 'Round Off'

+ +
+ +
+
+
+
+
+ +
+

Need Any Help?

+ +
diff --git a/round_off_invoices/static/description/ro1.png b/round_off_invoices/static/description/ro1.png new file mode 100644 index 000000000..7c44ec5c3 Binary files /dev/null and b/round_off_invoices/static/description/ro1.png differ diff --git a/round_off_invoices/static/description/ro2.png b/round_off_invoices/static/description/ro2.png new file mode 100644 index 000000000..f131f74c4 Binary files /dev/null and b/round_off_invoices/static/description/ro2.png differ diff --git a/round_off_invoices/static/description/ro3.png b/round_off_invoices/static/description/ro3.png new file mode 100644 index 000000000..5133d1ccc Binary files /dev/null and b/round_off_invoices/static/description/ro3.png differ diff --git a/round_off_invoices/static/description/ro4.png b/round_off_invoices/static/description/ro4.png new file mode 100644 index 000000000..84a99521d Binary files /dev/null and b/round_off_invoices/static/description/ro4.png differ diff --git a/round_off_invoices/static/description/ro6.png b/round_off_invoices/static/description/ro6.png new file mode 100644 index 000000000..2cb227cb7 Binary files /dev/null and b/round_off_invoices/static/description/ro6.png differ diff --git a/round_off_invoices/views/round_off_view.xml b/round_off_invoices/views/round_off_view.xml new file mode 100644 index 000000000..44a212480 --- /dev/null +++ b/round_off_invoices/views/round_off_view.xml @@ -0,0 +1,45 @@ + + + + + account.config.settings.roundoff + account.config.settings + + + + + + + + + + + + + invoice.line.extend_round_off + account.invoice + + + + + + + + + + + + + + + \ No newline at end of file