diff --git a/l10n_ro_account_period_close/__init__.py b/l10n_ro_account_period_close/__init__.py
new file mode 100644
index 000000000..aee8895e7
--- /dev/null
+++ b/l10n_ro_account_period_close/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizards
diff --git a/l10n_ro_account_period_close/__manifest__.py b/l10n_ro_account_period_close/__manifest__.py
new file mode 100644
index 000000000..b02b4d13e
--- /dev/null
+++ b/l10n_ro_account_period_close/__manifest__.py
@@ -0,0 +1,21 @@
+# Copyright 2018 Forest and Biomass Romania
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+{
+ 'name': 'Romania - Account Period Closing',
+ 'summary': 'Romania - Account Period Closing',
+ 'version': '11.0.1.0.0',
+ 'category': 'Localization',
+ 'author': 'Forest and Biomass Romania, '
+ 'Odoo Community Association (OCA)',
+ 'website': 'https://www.forbiom.eu',
+ 'license': 'AGPL-3',
+ 'installable': True,
+ 'depends': ['account'],
+ 'data': [
+ 'views/account_period_close_view.xml',
+ 'wizards/wizard_account_period_closing_view.xml',
+ 'security/account_security.xml',
+ 'security/ir.model.access.csv',
+ ],
+}
diff --git a/l10n_ro_account_period_close/models/__init__.py b/l10n_ro_account_period_close/models/__init__.py
new file mode 100644
index 000000000..1e271ee79
--- /dev/null
+++ b/l10n_ro_account_period_close/models/__init__.py
@@ -0,0 +1,2 @@
+from . import account
+from . import account_period_close
diff --git a/l10n_ro_account_period_close/models/account.py b/l10n_ro_account_period_close/models/account.py
new file mode 100644
index 000000000..af4de75d9
--- /dev/null
+++ b/l10n_ro_account_period_close/models/account.py
@@ -0,0 +1,22 @@
+# Copyright 2018 Forest and Biomass Romania
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo import fields, models
+
+
+class Account(models.Model):
+ _inherit = 'account.account'
+
+ close_check = fields.Boolean(
+ 'Bypass Closing Side Check',
+ help='By checking this when you close a period, it will not respect '
+ 'the side of closing, meaning: expenses closed on credit side, '
+ 'incomed closed on debit side. \n You should check the 711xxx '
+ 'accounts.')
+
+
+class AccountMove(models.Model):
+ _inherit = 'account.move'
+
+ close_id = fields.Many2one(
+ 'account.period.closing', 'Closed Account Period')
diff --git a/l10n_ro_account_period_close/models/account_period_close.py b/l10n_ro_account_period_close/models/account_period_close.py
new file mode 100644
index 000000000..edc1b77ac
--- /dev/null
+++ b/l10n_ro_account_period_close/models/account_period_close.py
@@ -0,0 +1,229 @@
+# Copyright 2018 Forest and Biomass Romania
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo import api, fields, models
+
+
+class AccountPeriodClosing(models.Model):
+ _name = 'account.period.closing'
+ _description = 'Account Period Closing'
+
+ name = fields.Char('Name', required=True)
+ company_id = fields.Many2one(
+ 'res.company', string='Company', required=True,
+ default=lambda self: self.env.user.company_id)
+ type = fields.Selection(
+ [
+ ('income', 'Incomes'),
+ ('expense', 'Expenses'),
+ ('selected', 'Selected')
+ ], string='Type', required=True)
+ close_result = fields.Boolean('Close debit and credit accounts')
+ journal_id = fields.Many2one(
+ 'account.journal', string='Journal', required=True)
+ account_ids = fields.Many2many(
+ 'account.account', string='Accounts to close')
+ debit_account_id = fields.Many2one(
+ 'account.account',
+ 'Closing account, debit',
+ required=True,
+ domain="[('company_id', '=', company_id)]"
+ )
+ credit_account_id = fields.Many2one(
+ 'account.account',
+ 'Closing account, credit',
+ required=True,
+ domain="[('company_id', '=', company_id)]"
+ )
+ move_ids = fields.One2many('account.move', 'close_id', 'Closing Moves')
+
+ @api.onchange('company_id', 'type')
+ def _onchange_type(self):
+ acc_type = False
+ accounts = self.env['account.account']
+ if self.type == 'income':
+ acc_type = self.env.ref(
+ 'account.data_account_type_revenue').id
+ elif self.type == 'expense':
+ acc_type = self.env.ref(
+ 'account.data_account_type_expenses').id
+ if acc_type:
+ accounts = self.env['account.account'].search([
+ ('user_type_id', '=', acc_type),
+ ('company_id', '=', self.company_id.id)
+ ])
+ self.account_ids = accounts
+
+ def _get_accounts(self, accounts, display_account):
+ """ compute the balance, debit and credit for the provided accounts
+ :Arguments:
+ `accounts`: list of accounts record,
+ `display_account`: it's used to display either all accounts or
+ those accounts which balance is > 0
+ :Returns a list of dict of Accounts with following key and value
+ `name`: Account name,
+ `code`: Account code,
+ `credit`: total amount of credit,
+ `debit`: total amount of debit,
+ `balance`: total amount of balance,
+ """
+
+ account_result = {}
+ # Prepare sql query base on selected parameters from wizard
+ tables, where_clause, where_params = self.env[
+ 'account.move.line']._query_get()
+ tables = tables.replace('"', '')
+ if not tables:
+ tables = 'account_move_line'
+ wheres = [""]
+ if where_clause.strip():
+ wheres.append(where_clause.strip())
+ filters = " AND ".join(wheres)
+ # compute the balance, debit and credit for the provided accounts
+ request = ("SELECT account_id AS id, "
+ "SUM(debit) AS debit, "
+ "SUM(credit) AS credit, "
+ "(SUM(debit) - SUM(credit)) AS balance" +
+ " FROM " + tables +
+ " WHERE account_id IN %s " + filters +
+ " GROUP BY account_id")
+ params = (tuple(accounts.ids),) + tuple(where_params)
+ self.env.cr.execute(request, params)
+ for row in self.env.cr.dictfetchall():
+ account_result[row.pop('id')] = row
+
+ account_res = []
+ for account in accounts:
+ res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance'])
+ currency = account.currency_id if account.currency_id else \
+ account.company_id.currency_id
+ res['id'] = account.id
+ res['code'] = account.code
+ res['name'] = account.name
+ if account.id in account_result:
+ res['debit'] = account_result[account.id].get('debit')
+ res['credit'] = account_result[account.id].get('credit')
+ res['balance'] = account_result[account.id].get('balance')
+ if display_account == 'all':
+ account_res.append(res)
+ if display_account == 'not_zero' and \
+ not currency.is_zero(res['balance']):
+ account_res.append(res)
+ if display_account == 'movement' and \
+ (not currency.is_zero(res['debit']) or
+ not currency.is_zero(res['credit'])):
+ account_res.append(res)
+ return account_res
+
+ @api.multi
+ def close(self, date_from=None, date_to=None):
+ """ This method will create the closing move for the
+ date interval selected."""
+ account_obj = self.env['account.account']
+ journal_id = self.journal_id.id
+ for closing in self:
+ ctx = self.env.context.copy()
+ ctx['strict_range'] = True
+ ctx['date_from'] = date_from
+ ctx['date_to'] = date_to
+ account_res = self.with_context(ctx)._get_accounts(
+ closing.account_ids, 'not_zero')
+ move = self.env['account.move'].create({
+ 'date': date_to,
+ 'journal_id': journal_id,
+ 'close_id': closing.id,
+ 'company_id': closing.company_id.id
+ })
+ amount = 0.0
+ for account in account_res:
+ if account['balance'] != 0.0:
+ balance = account['balance']
+ check = account_obj.browse(account['id']).close_check
+ if closing.type == 'expense' and not check:
+ val = {
+ 'name': 'Closing ' + closing.name,
+ 'move_id': move.id,
+ 'account_id': account['id'],
+ 'credit': balance or 0.0,
+ 'debit': 0.0,
+ }
+ elif closing.type == 'income' and not check:
+ val = {
+ 'name': 'Closing ' + closing.name,
+ 'move_id': move.id,
+ 'account_id': account['id'],
+ 'credit': 0.0,
+ 'debit': (-1 * balance) or 0.0,
+ }
+ else:
+ val = {
+ 'name': 'Closing ' + closing.name,
+ 'move_id': move.id,
+ 'account_id': account['id'],
+ 'credit': balance if balance > 0.0 else 0.0,
+ 'debit': -balance if balance < 0.0 else 0.0,
+ }
+ amount += balance
+ self.env['account.move.line'].with_context(
+ check_move_validity=False).create(val)
+
+ diff_line = {
+ 'name': 'Closing ' + closing.name,
+ 'move_id': move.id,
+ 'account_id':
+ closing.debit_account_id.id if
+ amount >= 0 else closing.credit_account_id.id,
+ 'credit': -amount if amount <= 0.0 else 0.0,
+ 'debit': amount if amount >= 0.0 else 0.0,
+ }
+ self.env['account.move.line'].with_context(
+ check_move_validity=False).create(diff_line)
+ if self.close_result and amount != 0.0:
+ debit_acc = closing.debit_account_id
+ credit_acc = closing.credit_account_id
+ debit = credit = new_amount = 0.0
+ ctx1 = dict(self._context)
+ ctx1.update({'date_from': False, 'date_to': date_to})
+ accounts = account_obj.browse(
+ [closing.debit_account_id.id,
+ closing.credit_account_id.id])
+ account_res = self.with_context(ctx1)._get_accounts(
+ accounts, 'all')
+ for acc in account_res:
+ if acc['id'] == closing.debit_account_id.id:
+ debit = acc['balance']
+ if acc['id'] == closing.credit_account_id.id:
+ credit = acc['balance']
+ old_balance = debit - (-1 * credit)
+ if credit and debit:
+ if old_balance > 0:
+ debit_acc = closing.credit_account_id
+ credit_acc = closing.debit_account_id
+ elif old_balance < 0:
+ debit_acc = closing.debit_account_id
+ credit_acc = closing.credit_account_id
+ if abs(debit) > abs(credit):
+ new_amount = -1 * credit
+ else:
+ new_amount = debit
+ diff_line = {
+ 'name': 'Closing ' + closing.name +
+ ' ' + str(debit_acc.code),
+ 'move_id': move.id,
+ 'account_id': debit_acc.id,
+ 'credit': 0.0,
+ 'debit': new_amount,
+ }
+ self.env['account.move.line'].with_context(
+ check_move_validity=False).create(diff_line)
+ diff_line = {
+ 'name': 'Closing ' + closing.name +
+ ' ' + str(credit_acc.code),
+ 'move_id': move.id,
+ 'account_id': credit_acc.id,
+ 'credit': new_amount,
+ 'debit': 0.0,
+ }
+ self.env['account.move.line'].with_context(
+ check_move_validity=False).create(diff_line)
+ move.post()
diff --git a/l10n_ro_account_period_close/security/account_security.xml b/l10n_ro_account_period_close/security/account_security.xml
new file mode 100644
index 000000000..e87d6a629
--- /dev/null
+++ b/l10n_ro_account_period_close/security/account_security.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ Account Period Closing
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
diff --git a/l10n_ro_account_period_close/security/ir.model.access.csv b/l10n_ro_account_period_close/security/ir.model.access.csv
new file mode 100644
index 000000000..7071f08c2
--- /dev/null
+++ b/l10n_ro_account_period_close/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_period_closing_user,account.period.closing,model_account_period_closing,account.group_account_user,1,1,1,0
+access_account_period_closing_manager,account.period.closing,model_account_period_closing,account.group_account_manager,1,1,1,1
diff --git a/l10n_ro_account_period_close/static/description/icon.png b/l10n_ro_account_period_close/static/description/icon.png
new file mode 100644
index 000000000..7ba9c1019
Binary files /dev/null and b/l10n_ro_account_period_close/static/description/icon.png differ
diff --git a/l10n_ro_account_period_close/views/account_period_close_view.xml b/l10n_ro_account_period_close/views/account_period_close_view.xml
new file mode 100644
index 000000000..84bcc45b8
--- /dev/null
+++ b/l10n_ro_account_period_close/views/account_period_close_view.xml
@@ -0,0 +1,81 @@
+
+
+
+ account.account.form
+ account.account
+ form
+
+
+
+
+
+
+
+
+
+ account.period.closing.tree
+ account.period.closing
+ form
+
+
+
+
+
+
+
+
+
+
+
+ account.period.closing.form
+ account.period.closing
+ form
+
+
+
+
+
+
+ Account Period Closing
+ account.period.closing
+ form
+ tree,form
+
+
+
+
+
+
+
diff --git a/l10n_ro_account_period_close/wizards/__init__.py b/l10n_ro_account_period_close/wizards/__init__.py
new file mode 100644
index 000000000..db467347a
--- /dev/null
+++ b/l10n_ro_account_period_close/wizards/__init__.py
@@ -0,0 +1 @@
+from . import wizard_account_period_closing
diff --git a/l10n_ro_account_period_close/wizards/wizard_account_period_closing.py b/l10n_ro_account_period_close/wizards/wizard_account_period_closing.py
new file mode 100644
index 000000000..a4a3592e1
--- /dev/null
+++ b/l10n_ro_account_period_close/wizards/wizard_account_period_closing.py
@@ -0,0 +1,52 @@
+# Copyright 2018 Forest and Biomass Romania
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from dateutil.relativedelta import relativedelta
+
+from odoo import api, fields, models
+
+
+class WizardAccountPeriodClosing(models.TransientModel):
+ _name = "account.period.closing.wizard"
+ _description = "Wizard for Account Period Closing"
+
+ def _get_default_date_from(self):
+ today = fields.Date.from_string(fields.Date.today())
+ return today + relativedelta(day=1, months=-1)
+
+ def _get_default_date_to(self):
+ today = fields.Date.from_string(fields.Date.today())
+ return today + relativedelta(day=1, days=-1)
+
+ closing_id = fields.Many2one(
+ "account.period.closing",
+ "Closing Model",
+ required=True,
+ ondelete="cascade"
+ )
+ company_id = fields.Many2one(
+ comodel_name="res.company",
+ related="closing_id.company_id")
+ journal_id = fields.Many2one(
+ comodel_name="account.journal",
+ related="closing_id.journal_id")
+ date_range_id = fields.Many2one(
+ comodel_name="date.range",
+ string="Date range")
+ date_from = fields.Date("Start Date", required=True,
+ default=_get_default_date_from)
+ date_to = fields.Date("End Date", required=True,
+ default=_get_default_date_to)
+
+ @api.onchange('date_range_id')
+ def onchange_date_range_id(self):
+ """Handle date range change."""
+ if self.date_range_id:
+ self.date_from = self.date_range_id.date_start
+ self.date_to = self.date_range_id.date_end
+
+ @api.multi
+ def do_close(self):
+ self.ensure_one()
+ self.closing_id.close(self.date_from, self.date_to)
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/l10n_ro_account_period_close/wizards/wizard_account_period_closing_view.xml b/l10n_ro_account_period_close/wizards/wizard_account_period_closing_view.xml
new file mode 100644
index 000000000..26c296932
--- /dev/null
+++ b/l10n_ro_account_period_close/wizards/wizard_account_period_closing_view.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ account.period.closing.wizard
+ account.period.closing.wizard
+
+
+
+
+
diff --git a/oca_dependencies.txt b/oca_dependencies.txt
new file mode 100644
index 000000000..9b5df92e9
--- /dev/null
+++ b/oca_dependencies.txt
@@ -0,0 +1 @@
+server-ux