[IMP] account: facilitate bank account and bank journal config

With this change,
 * some modules are now set to be automatically installed by default
 * the setup bar of accounting has been changed in order to allow more easiliy the configuration of bank accounts/journals (and the step to oversee the opening entry has been removed)
 * the kanban view in the dashboard has been enhanced (mostly for bank journals, but not only)

Was task: 32668
Was PR #23049
This commit is contained in:
Olivier Colson
2018-06-08 17:48:03 +02:00
committed by qdp-odoo
parent c879fb0f00
commit b30fed7a7b
22 changed files with 280 additions and 305 deletions
+2 -1
View File
@@ -42,7 +42,7 @@ def _auto_install_l10n(cr, registry):
if country_code in ['US', 'AU', 'NZ', 'CA', 'CO', 'EC', 'ES', 'FR', 'IN', 'MX', 'UK']:
module_list.append('account_yodlee')
if country_code in SYSCOHADA_LIST + [
'AT', 'BE', 'CA', 'CO', 'DE', 'EC', 'ES', 'ET', 'FR', 'GR', 'IT', 'LU', 'MX', 'NL', 'NO',
'AT', 'BE', 'CA', 'CO', 'DE', 'EC', 'ES', 'ET', 'FR', 'GR', 'IT', 'LU', 'MX', 'NL', 'NO',
'PL', 'PT', 'RO', 'SI', 'TR', 'UK', 'VE', 'VN'
]:
module_list.append('base_vat')
@@ -53,5 +53,6 @@ def _auto_install_l10n(cr, registry):
europe_country_codes = [x.code for x in europe.country_ids]
if country_code in europe_country_codes:
module_list.append('account_sepa')
module_list.append('account_bank_statement_import_camt')
module_ids = env['ir.module.module'].search([('name', 'in', module_list), ('state', '=', 'uninstalled')])
module_ids.sudo().button_install()
+26 -19
View File
@@ -358,6 +358,12 @@ class AccountJournal(models.Model):
def _default_outbound_payment_methods(self):
return self.env.ref('account.account_payment_method_manual_out')
def __get_bank_statements_available_sources(self):
return [('undefined', _('Undefined Yet'))]
def _get_bank_statements_available_sources(self):
return self.__get_bank_statements_available_sources()
name = fields.Char(string='Journal Name', required=True)
code = fields.Char(string='Short Code', size=5, required=True, help="The journal entries of this journal will be named using this prefix.")
active = fields.Boolean(default=True, help="Set active to false to hide the Journal without removing it.")
@@ -405,12 +411,12 @@ class AccountJournal(models.Model):
refund_sequence = fields.Boolean(string='Dedicated Credit Note Sequence', help="Check this box if you don't want to share the same sequence for invoices and credit notes made from this journal", default=False)
inbound_payment_method_ids = fields.Many2many('account.payment.method', 'account_journal_inbound_payment_method_rel', 'journal_id', 'inbound_payment_method',
domain=[('payment_type', '=', 'inbound')], string='Debit Methods', default=lambda self: self._default_inbound_payment_methods(),
domain=[('payment_type', '=', 'inbound')], string='For Incoming Payments', default=lambda self: self._default_inbound_payment_methods(),
help="Manual: Get paid by cash, check or any other method outside of Odoo.\n"\
"Electronic: Get paid automatically through a payment acquirer by requesting a transaction on a card saved by the customer when buying or subscribing online (payment token).\n"\
"Batch Deposit: Encase several customer checks at once by generating a batch deposit to submit to your bank. When encoding the bank statement in Odoo,you are suggested to reconcile the transaction with the batch deposit. Enable this option from the settings.")
outbound_payment_method_ids = fields.Many2many('account.payment.method', 'account_journal_outbound_payment_method_rel', 'journal_id', 'outbound_payment_method',
domain=[('payment_type', '=', 'outbound')], string='Payment Methods', default=lambda self: self._default_outbound_payment_methods(),
domain=[('payment_type', '=', 'outbound')], string='For Outgoing Payments', default=lambda self: self._default_outbound_payment_methods(),
help="Manual:Pay bill by cash or any other method outside of Odoo.\n"\
"Check:Pay bill by check and print it from Odoo.\n"\
"SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your bank. Enable this option from the settings.")
@@ -424,7 +430,7 @@ class AccountJournal(models.Model):
# Bank journals fields
company_partner_id = fields.Many2one('res.partner', related='company_id.partner_id', string='Account Holder', readonly=True, store=False)
bank_account_id = fields.Many2one('res.partner.bank', string="Bank Account", ondelete='restrict', copy=False, domain="[('partner_id','=', company_partner_id)]")
bank_statements_source = fields.Selection([('undefined', 'Undefined'),('manual', 'Record Manually')], string='Bank Feeds', default='undefined')
bank_statements_source = fields.Selection(selection=_get_bank_statements_available_sources, string='Bank Feeds', default='undefined', help="Defines how the bank statements will be registered")
bank_acc_number = fields.Char(related='bank_account_id.acc_number')
bank_id = fields.Many2one('res.bank', related='bank_account_id.bank_id')
@@ -681,6 +687,16 @@ class AccountJournal(models.Model):
'company_id': company.id,
}
@api.model
def get_next_bank_cash_default_code(self, journal_type, company_id):
journal_code_base = (journal_type == 'cash' and 'CSH' or 'BNK')
journals = self.env['account.journal'].search([('code', 'like', journal_code_base + '%'), ('company_id', '=', company_id)])
for num in range(1, 100):
# journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
journal_code = journal_code_base + str(num)
if journal_code not in journals.mapped('code'):
return journal_code
@api.model
def create(self, vals):
company_id = vals.get('company_id', self.env.user.company_id.id)
@@ -691,15 +707,8 @@ class AccountJournal(models.Model):
# If no code provided, loop to find next available journal code
if not vals.get('code'):
journal_code_base = (vals['type'] == 'cash' and 'CSH' or 'BNK')
journals = self.env['account.journal'].search([('code', 'like', journal_code_base + '%'), ('company_id', '=', company_id)])
for num in range(1, 100):
# journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
journal_code = journal_code_base + str(num)
if journal_code not in journals.mapped('code'):
vals['code'] = journal_code
break
else:
vals['code'] = self.get_next_bank_cash_default_code(vals['type'], company_id)
if not vals['code']:
raise UserError(_("Cannot generate an unused journal code. Please fill the 'Shortcode' field."))
# Create a default debit/credit account if not given
@@ -780,14 +789,12 @@ class AccountJournal(models.Model):
journal.at_least_one_inbound = bool(len(journal.inbound_payment_method_ids))
journal.at_least_one_outbound = bool(len(journal.outbound_payment_method_ids))
def setup_save_journal_and_create_more(self):
""" This function is triggered by the button allowing to create more
bank accounts, displayed in the "Bank Accounts" wizard of the setup bar.
Button execution is done in Python, so that the model is validated and saved
before executing the action.
def action_configure_bank_journal(self):
""" This function is called by the "configure" button of bank journals,
visible on dashboard if no bank statement source has been defined yet
"""
return self.env.ref('account.action_account_bank_journal_form').read()[0]
# We simply call the setup bar function.
return self.env['res.company'].setting_init_bank_account_action()
class ResPartnerBank(models.Model):
@@ -411,7 +411,6 @@ class account_journal(models.Model):
@api.multi
def create_bank_statement(self):
"""return action to create a bank statements. This button should be called only on journals with type =='bank'"""
self.bank_statements_source = 'manual'
action = self.env.ref('account.action_bank_statement_tree').read()[0]
action.update({
'views': [[False, 'form']],
+3 -6
View File
@@ -622,9 +622,6 @@ class AccountMoveLine(models.Model):
help="Technical field used to mark a tax line as exigible in the vat report or not (only exigible journal items are displayed). By default all new journal items are directly exigible, but with the feature cash_basis on taxes, some will become exigible only when the payment is recorded.")
parent_state = fields.Char(compute="_compute_parent_state", help="State of the parent account.move")
#Needed for setup, as a decoration attribute needs to know that for a tree view in one of the popups, and there's no way to reference directly a xml id from there
is_unaffected_earnings_line = fields.Boolean(string="Is Unaffected Earnings Line", compute="_compute_is_unaffected_earnings_line", help="Tells whether or not this line belongs to an unaffected earnings account")
recompute_tax_line = fields.Boolean(store=False, help="Technical field used to know if the tax_ids field has been modified in the UI.")
tax_line_grouping_key = fields.Char(store=False, string='Old Taxes', help="Technical field used to store the old values of fields used to compute tax lines (in account.move form view) between the moment the user changed it and the moment the ORM reflects that change in its one2many")
@@ -730,7 +727,7 @@ class AccountMoveLine(models.Model):
multiple_currency = True
else:
currency = list(currency)[0]
# Get the sum(debit, credit, amount_currency) of all amls involved
# Get the sum(debit, credit, amount_currency) of all amls involved
total_debit = 0
total_credit = 0
total_amount_currency = 0
@@ -802,7 +799,7 @@ class AccountMoveLine(models.Model):
amount_reconcile = min(debit_move[field], -credit_move[field])
#Remove from recordset the one(s) that will be totally reconciled
# For optimization purpose, the creation of the partial_reconcile are done at the end,
# For optimization purpose, the creation of the partial_reconcile are done at the end,
# therefore during the process of reconciling several move lines, there are actually no recompute performed by the orm
# and thus the amount_residual are not recomputed, hence we have to do it manually.
if amount_reconcile == debit_move[field]:
@@ -836,7 +833,7 @@ class AccountMoveLine(models.Model):
'currency_id': currency,
})
part_rec = self.env['account.partial.reconcile']
part_rec = self.env['account.partial.reconcile']
index = 0
with self.env.norecompute():
for partial_rec_dict in to_create:
+8 -19
View File
@@ -246,27 +246,16 @@ Best Regards,'''))
@api.model
def setting_init_bank_account_action(self):
""" Called by the 'Bank Accounts' button of the setup bar."""
company = self.env.user.company_id
view_id = self.env.ref('account.setup_bank_journal_form').id
res = {
'type': 'ir.actions.act_window',
'name': _('Bank Account'),
'view_mode': 'form',
'res_model': 'account.journal',
'target': 'new',
'views': [[view_id, 'form']],
view_id = self.env.ref('account.setup_bank_account_wizard').id
return {'type': 'ir.actions.act_window',
'name': _('Create a Bank Account'),
'res_model': 'account.setup.bank.manual.config',
'target': 'new',
'view_mode': 'form',
'view_type': 'form',
'views': [[view_id, 'form']],
}
# If some bank journal already exists, we open it in the form, so the user can edit it.
# Otherwise, we just open the form in creation mode.
bank_journal = self.env['account.journal'].search([('company_id','=', company.id), ('type','=','bank')], limit=1)
if bank_journal:
res['res_id'] = bank_journal.id
else:
res['context'] = {'default_type': 'bank'}
return res
@api.model
def setting_init_fiscal_year_action(self):
""" Called by the 'Fiscal Year Opening' button of the setup bar."""
@@ -160,6 +160,14 @@ var AccountSetupBarController = KanbanController.extend({
company_button_action: '_triggerCompanyButtonAction',
}),
start: function () {
this._super();
var self = this;
core.bus.on('refresh_account_dashboard', this, function (){
self.trigger_up('reload')
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
@@ -5,11 +5,6 @@
<div class="o_account_dashboard_header hidden-xs o_form_view" t-if="values['show_setup_bar']">
<div class="o_form_statusbar">
<div class="o_statusbar_status" data-original-title="" title="" >
<button name="setting_opening_move_action" type="company_object" t-attf-class="btn btn-sm o_arrow_button btn-default account_setup_dashboard_action #{values['initial_balance'] and 'o_action_done' or ''}">
<i class="fa fa-check" t-if="values['initial_balance']"/>
<b class="o_account_dashboard_index">5</b>
Initial Balances
</button>
<button name="setting_chart_of_accounts_action" type="company_object" t-attf-class="btn btn-sm o_arrow_button btn-default account_setup_dashboard_action #{values['chart_of_accounts'] and 'o_action_done' or ''}" >
<i class="fa fa-check" t-if="values['chart_of_accounts']"/>
<b class="o_account_dashboard_index">4</b>
@@ -23,7 +18,7 @@
<button name="setting_init_bank_account_action" type="company_object" t-attf-class="btn btn-sm o_arrow_button btn-default account_setup_dashboard_action #{values['bank'] and 'o_action_done' or ''}">
<i class="fa fa-check" t-if="values['bank']"/>
<b class="o_account_dashboard_index">2</b>
Bank Accounts
Add a Bank Account
</button>
<button name="setting_init_company_action" type="company_object" t-attf-class="btn btn-sm o_arrow_button btn-default account_setup_dashboard_action #{values['company'] and 'o_action_done' or ''}">
<i class="fa fa-check" t-if="values['company']"/>
-1
View File
@@ -13,7 +13,6 @@ from . import test_payment
from . import test_product_id_change
from . import test_reconciliation
from . import test_search
from . import test_setup_bar
from . import test_tax
from . import test_account_move_taxes_edition
from . import test_templates_consistency
-77
View File
@@ -1,77 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestSetupBar(AccountingTestCase):
def test_initial_balance_step(self):
company = self.env.ref('base.main_company')
company.create_op_move_if_non_existant()
initial_setup_wizard = self.env['account.opening'].create({'company_id': company.id}).with_context({'check_move_validity': False})
unaffected_earnings_type = self.env.ref("account.data_unaffected_earnings")
account = self.env['account.account'].search([('user_type_id', '!=', unaffected_earnings_type.id)], limit=1)
# Adding the first line creates a new automatic adjustment line balancing the move
test_line_1 = self.env['account.move.line'].with_context({'check_move_validity': False}).create({
'name': 'Test line 1',
'move_id': company.account_opening_move_id.id,
'account_id': account.id,
'debit': 42.0,
'credit': 0.0,
'company_id': company.id,
})
initial_setup_wizard.opening_move_line_ids_changed()
self.assertEqual(len(initial_setup_wizard.opening_move_line_ids), 2, "The wizard should contain 2 lines: 1 manually created, and 1 automatic adjustment.")
automatic_line = initial_setup_wizard.opening_move_line_ids.filtered(lambda x: x != test_line_1)
self.assertEqual(automatic_line.account_id.user_type_id, unaffected_earnings_type, "Automatic adjustment line should be of type 'current year earnings'.")
self.assertEqual(automatic_line.credit, 42.0, "Automatic line should balance opening move.")
self.assertEqual(automatic_line.debit, 0.0, "Automatic line should balance opening move.")
# Adding a new line modifies the amount of the already existing adjustment line
test_line_2 = self.env['account.move.line'].with_context({'check_move_validity': False}).create({
'name': 'Test line 2',
'move_id': company.account_opening_move_id.id,
'account_id': account.id,
'debit': 0.0,
'credit': 12.0,
'company_id': company.id,
})
initial_setup_wizard.opening_move_line_ids_changed()
self.assertEqual(len(initial_setup_wizard.opening_move_line_ids), 3, "The wizard should contain 3 lines: 2 manually created, and 1 automatic adjustment.")
self.assertTrue(automatic_line in initial_setup_wizard.opening_move_line_ids, "Automatic line should stay the same when adding a new line.")
self.assertEqual(automatic_line.credit, 30.0, "Automatic line should balance opening move.")
self.assertEqual(automatic_line.debit, 0.0, "Automatic line should balance opening move.")
# When a new line balances the move, the adjustment line gets automatically removed
test_line_3 = self.env['account.move.line'].with_context({'check_move_validity': False}).create({
'name': 'Test line 3',
'move_id': company.account_opening_move_id.id,
'account_id': account.id,
'debit': 0.0,
'credit': 30.0,
'company_id': company.id,
})
initial_setup_wizard.opening_move_line_ids_changed()
self.assertEqual(len(initial_setup_wizard.opening_move_line_ids), 3, "The wizard should contain 3 lines: 3 manually created, and 0 automatic adjustment.")
self.assertFalse(automatic_line in initial_setup_wizard.opening_move_line_ids, "Automatic adjustment line should be removed when useless.")
# The opening move stays balanced at any time
test_line_4 = self.env['account.move.line'].with_context({'check_move_validity': False}).create({
'name': 'Test line 4',
'move_id': company.account_opening_move_id.id,
'account_id': account.id,
'debit': 11.0,
'credit': 0.0,
'company_id': company.id,
})
initial_setup_wizard.opening_move_line_ids_changed()
initial_setup_wizard.validate()
company.account_opening_move_id.assert_balanced()
@@ -224,13 +224,11 @@
<button type="object" name="action_open_reconcile" class="btn btn-primary btn-sm"> Reconcile <t t-esc="dashboard.number_to_reconcile"/> Items</button>
</t>
<t t-if="journal_type == 'bank'">
<div name="bank_journal_default_cta" t-if="dashboard.bank_statements_source == 'undefined'">
<button t-if="dashboard.number_to_reconcile == 0" type="object" name="create_bank_statement" class="btn btn-primary btn-sm">New Statement</button>
<a t-if="dashboard.number_to_reconcile > 0" type="object" name="create_bank_statement" class="oe_inline">New Statement</a>
</div>
<div name="bank_journal_cta" t-if="dashboard.bank_statements_source != 'undefined'">
<button t-if="dashboard.bank_statements_source == 'manual' &amp;&amp; dashboard.number_to_reconcile == 0" type="object" name="create_bank_statement" class="btn btn-primary btn-sm">New Statement</button>
<a t-if="dashboard.bank_statements_source == 'manual' &amp;&amp; dashboard.number_to_reconcile > 0" type="object" name="create_bank_statement" class="oe_inline">New Statement</a>
<div name="bank_journal_cta">
<button t-if="dashboard.bank_statements_source == 'undefined'" name="action_configure_bank_journal" type="object" class="btn btn-primary btn-sm">Configure</button>
<div name="bank_statement_create_button" t-if="dashboard.bank_statements_source != 'online_sync'">
<a type="object" name="create_bank_statement" class="oe_inline">Create</a><span name="button_import_placeholder"/> Statements
</div>
</div>
</t>
<t t-if="dashboard.number_to_reconcile > 0">
@@ -289,12 +287,12 @@
<div class="col-xs-7">
<a type="object" t-if="journal_type == 'sale'" name="open_action"
context="{'search_default_unpaid':1}" id="account_dashboard_sale_pay_link">
<t t-esc="dashboard.number_waiting"/> Payments to Receive
<t t-esc="dashboard.number_waiting"/> Unpaid Invoices
</a>
<a type="object" t-if="journal_type == 'purchase'" name="open_action"
context="{'search_default_unpaid':1}" id="account_dashboard_purchase_pay_link">
<t t-esc="dashboard.number_waiting"/> Payments to Do
<t t-esc="dashboard.number_waiting"/> Bills to Pay
</a>
</div>
<div class="col-xs-5 text-right">
+19 -21
View File
@@ -316,6 +316,18 @@
</group>
</group>
</page>
<page name="bank_account" string="Bank Account" attrs="{'invisible': [('type', '!=', 'bank')]}">
<group>
<group>
<field name="company_partner_id" invisible="1"/>
<field name="bank_account_id"/>
<field name="bank_id"/>
</group>
<group >
<field name="bank_statements_source" widget="radio" attrs="{'required': [('type', '=', 'bank')]}" groups="account.group_account_user"/>
</group>
</group>
</page>
<page name="advanced_settings" string="Advanced Settings">
<group>
<group string="Control-Access" groups="base.group_no_one">
@@ -323,15 +335,14 @@
<field name="type_control_ids" widget="many2many_tags"/>
<field name="account_control_ids" widget="many2many_tags"/>
</group>
<group string="Miscellaneous">
<group string="Payment Method Types">
<field name="inbound_payment_method_ids" widget="many2many_checkboxes" attrs="{'invisible': [('type', 'not in', ['bank', 'cash'])]}"/>
<field name="outbound_payment_method_ids" widget="many2many_checkboxes" attrs="{'invisible': [('type', 'not in', ['bank', 'cash'])]}"/>
<field name="group_invoice_lines" attrs="{'invisible': [('type', 'not in', ['sale', 'purchase'])]}"/>
</group>
<group string="Accounting App Options" groups="account.group_account_user">
<field name="profit_account_id" attrs="{'invisible': [('type', '!=', 'cash')]}"/>
<field name="loss_account_id" attrs="{'invisible': [('type', '!=', 'cash')]}"/>
<field name="show_on_dashboard" groups="base.group_no_one"/>
<group string="Accounting App Options" groups="account.group_account_user" attrs="{'invisible': [('type', '!=', 'cash')]}">
<field name="profit_account_id"/>
<field name="loss_account_id"/>
</group>
<group name="group_alias" string="Email your Vendor Bills" attrs="{'invisible': [('type', '!=', 'purchase')]}">
<label string="Email Alias" attrs="{'invisible': [('alias_domain', '=', False)]}"/>
@@ -347,17 +358,6 @@
</group>
</group>
</page>
<page name="bank_account" string="Bank Account" attrs="{'invisible': [('type', '!=', 'bank')]}">
<group>
<group>
<field name="bank_account_id"/>
<field name="bank_id"/>
</group>
<group >
<field name="bank_statements_source" widget="radio" attrs="{'required': [('type', '=', 'bank')]}" groups="account.group_account_user"/>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
@@ -412,7 +412,7 @@
<group>
<group>
<field name="bank_acc_number"/>
<field name="bank_id"/>
<field name="bank_id" readonly="1"/>
</group>
<group>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
@@ -421,10 +421,8 @@
</group>
<group>
<div name="advanced_info"> <!--The content of this div is hidden in the setup wizard inheriting this form view-->
<group name="receivables" string="Receivables">
<group string="Payment Method Types">
<field name="inbound_payment_method_ids" widget="many2many_checkboxes"/>
</group>
<group name="payables" string="Payables">
<field name="outbound_payment_method_ids" widget="many2many_checkboxes"/>
</group>
</div>
@@ -1572,7 +1570,7 @@
<group>
<field name="auto_reverse" attrs="{'readonly':[('reverse_entry_id', '!=', False)]}"/>
<field name="reverse_date" attrs="{'invisible':[('auto_reverse', '=', False)], 'required':[('auto_reverse', '=', True)],'readonly':[('reverse_entry_id', '!=', False)]}"/>
<field name="reverse_entry_id" attrs="{'invisible':True}"/>
<field name="reverse_entry_id" attrs="{'invisible':True}"/>
</group>
</page>
</notebook>
+56 -42
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo import api, fields, models
class FinancialYearOpeningWizard(models.TransientModel):
@@ -48,47 +48,61 @@ class FinancialYearOpeningWizard(models.TransientModel):
return super(FinancialYearOpeningWizard, self).write(vals)
class OpeningAccountMoveWizard(models.TransientModel):
_name = 'account.opening'
class SetupBarBankConfigWizard(models.TransientModel):
_inherits = {'res.partner.bank': 'res_partner_bank_id'}
_name = 'account.setup.bank.manual.config'
company_id = fields.Many2one(comodel_name='res.company', required=True)
opening_move_id = fields.Many2one(string='Opening Journal Entry', comodel_name='account.move', related='company_id.account_opening_move_id')
currency_id = fields.Many2one(comodel_name='res.currency', related='opening_move_id.currency_id')
opening_move_line_ids = fields.One2many(string='Opening Journal Items', related="opening_move_id.line_ids")
journal_id = fields.Many2one(string='Journal', comodel_name='account.journal', required=True, related='opening_move_id.journal_id')
date = fields.Date(string='Opening Date', required=True, related='opening_move_id.date')
create_or_link_option = fields.Selection(selection=[('new', 'Create new journal'), ('link', 'Link to an existing journal')], default='new')
new_journal_name = fields.Char(compute='compute_new_journal_related_data', inverse='set_linked_journal_id', required=True, help='Will be used to name the Journal related to this bank account')
linked_journal_id = fields.Many2one(string="Journal", comodel_name='account.journal', compute='compute_linked_journal_id', inverse='set_linked_journal_id')
new_journal_code = fields.Char(string="Code", required=True, default=lambda self: self.env['account.journal'].get_next_bank_cash_default_code('bank', self.env['res.company']._company_default_get('account.journal').id))
# field computing the type of the res.patrner.bank. It's behaves the same as a related res_part_bank_id.acc_type
# except we want to display this information while the record isn't yet saved.
related_acc_type = fields.Selection(string="Account Type", selection=lambda x: x.env['res.partner.bank'].get_supported_account_types(), compute='_compute_related_acc_type')
@api.depends('acc_number')
def _compute_related_acc_type(self):
for record in self:
record.related_acc_type = self.env['res.partner.bank'].retrieve_acc_type(record.acc_number)
@api.model
def create(self, vals):
""" This wizard is only used to setup an account for the current active
company, so we always inject the corresponding partner when creating
the model.
"""
vals['partner_id'] = self.env.user.company_id.partner_id.id
return super(SetupBarBankConfigWizard, self).create(vals)
@api.depends('linked_journal_id')
def compute_new_journal_related_data(self):
for record in self:
if record.linked_journal_id:
record.new_journal_name = record.linked_journal_id.name
@api.depends('journal_id') # Despite its name, journal_id is actually a One2many field
def compute_linked_journal_id(self):
for record in self:
record.linked_journal_id = record.journal_id and record.journal_id[0] or None
def set_linked_journal_id(self):
""" Called when saving the wizard.
"""
for record in self:
selected_journal = record.linked_journal_id
if record.create_or_link_option == 'new':
company = self.env['res.company']._company_default_get('account.journal')
selected_journal = self.env['account.journal'].create({
'name': record.new_journal_name,
'code': record.new_journal_code,
'type': 'bank',
'company_id': company.id,
'bank_account_id': record.res_partner_bank_id.id,
})
def validate(self):
self.opening_move_id.post()
return {
"effect": {
'fadeout': 'slow',
'img_url': '/web/static/src/img/smile.svg',
'message': _("Well done! The configuration steps have been done successfully."),
'type': "rainbow_man"
}
}
@api.onchange('opening_move_line_ids')
def opening_move_line_ids_changed(self):
debit_diff, credit_diff = self.company_id.get_opening_move_differences(self.opening_move_line_ids)
unaffected_earnings_account = self.company_id.get_unaffected_earnings_account()
balancing_line = self.opening_move_line_ids.filtered(lambda x: x.account_id == unaffected_earnings_account)
if balancing_line:
if not self.opening_move_line_ids == balancing_line and (debit_diff or credit_diff):
balancing_line.debit = credit_diff
balancing_line.credit = debit_diff
else:
self.opening_move_line_ids -= balancing_line
elif debit_diff or credit_diff:
balancing_line = self.env['account.move.line'].new({
'name': _('Automatic Balancing Line'),
'move_id': self.company_id.account_opening_move_id.id,
'account_id': unaffected_earnings_account.id,
'debit': credit_diff,
'credit': debit_diff,
'company_id': self.company_id,
})
self.opening_move_line_ids += balancing_line
""" Called by the validation button of this wizard. Serves as an
extension hook in account_bank_statement_import.
"""
pass
+27 -69
View File
@@ -59,88 +59,46 @@
</field>
</record>
<record id="setup_bank_journal_form" model="ir.ui.view">
<field name="name">account.setup.bank.journal.form</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_bank_journal_form"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//form/sheet" position="inside">
<button type="object" name="setup_save_journal_and_create_more">You can define additional accounts here</button>
</xpath>
<xpath expr="//div[@name='advanced_info']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//form/sheet" position="after">
<footer>
<field name="account_setup_bank_data_done" invisible="True"/>
<button string="Mark as done" type="object" name="mark_bank_setup_as_done_action" class="oe_highlight" attrs="{'invisible': [('account_setup_bank_data_done','=',True)]}"/>
<button string="Save" type="action" name="account.setup_wizard_close" attrs="{'invisible': [('account_setup_bank_data_done','=',True)]}"/>
<button string="Save" type="action" name="account.setup_wizard_close" class="oe_highlight" attrs="{'invisible': [('account_setup_bank_data_done','=',False)]}"/>
<button string="Unmark as done" type="object" name="unmark_bank_setup_as_done_action" attrs="{'invisible': [('account_setup_bank_data_done','=',False)]}"/>
</footer>
</xpath>
</field>
</record>
<record id="setup_opening_move_wizard_form" model="ir.ui.view">
<field name="name">account.setup.opening.move.wizard.form</field>
<field name="model">account.opening</field>
<record id="setup_bank_account_wizard" model="ir.ui.view">
<field name="name">account.online.sync.res.partner.bank.setup.form</field>
<field name="model">account.setup.bank.manual.config</field>
<field name="arch" type="xml">
<form>
<group>
<group>
<field name="journal_id"/>
<sheet>
<field name="create_or_link_option" widget="radio" options="{'horizontal': true}"/>
<group attrs="{'invisible': [('create_or_link_option','!=','link')]}">
<group>
<field name="linked_journal_id" attrs="{'required': [('create_or_link_option','=','link')]}" domain="[('type','=','bank'), ('bank_account_id', '=', False)]"/>
</group>
<group>
<span class="text-muted">Only journals not yet linked to a bank account are proposed</span>
</group>
</group>
<div attrs="{'invisible': [('create_or_link_option', '=', 'link')]}" class="oe_title">
<label for="new_journal_name" class="oe_edit_only"/>
<h1><field name="new_journal_name" placeholder="e.g Checking account" class="oe_inline"/></h1>
</div>
<group>
<field name="date"/>
<group>
<field name="acc_number" placeholder="e.g BE15001559627230"/>
<field name="bank_id" placeholder="e.g Bank of America"/>
<field name="new_journal_code" attrs="{'invisible': [('create_or_link_option', '=', 'link')]}"/>
</group>
<group>
<field name="related_acc_type" attrs="{'invisible': [('acc_number','=',False)]}"/>
</group>
</group>
</group>
<field name="company_id" invisible="1"/>
<field name="opening_move_line_ids" context="{'tree_view_ref': 'account.setup_opening_move_lines_tree'}"/>
</sheet>
<footer>
<button string="Save" type="action" name="account.setup_wizard_close"/>
<button string="Discard" special="cancel"/>
<button string="Post" name="validate" type="object" class="oe_highlight"/>
<button string="Create" class="oe_highlight" type="object" name="validate"/>
<button string="Cancel" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="setup_posted_move_form" model="ir.ui.view">
<field name="name">account.setup.posted.move.form</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//form/sheet" position="after">
<footer>
<button string="Save" type="action" class="oe_highlight" name="account.setup_wizard_close"/>
</footer>
</xpath>
</field>
</record>
<record id="setup_opening_move_lines_tree" model="ir.ui.view">
<field name="name">account.setup.opening.move.lines.tree</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<tree editable="bottom" string="Journal Items" decoration-info="is_unaffected_earnings_line == True">
<field name="account_id" domain="[('company_id', '=', parent.company_id), ('deprecated', '=', False)]"/>
<field name="partner_id"/>
<field name="name"/>
<field name="amount_currency" groups="base.group_multi_currency"/>
<field name="currency_id" options="{'no_create': True}" groups="base.group_multi_currency"/>
<field name="debit" sum="Total Debit"/>
<field name="credit" sum="Total Credit"/>
<field name="company_id" invisible="True"/>
<field name="is_unaffected_earnings_line" invisible="True"/>
</tree>
</field>
</record>
<record id="init_accounts_tree" model="ir.ui.view">
<field name="name">account.setup.opening.move.line.tree</field>
<field name="model">account.account</field>
@@ -12,6 +12,7 @@
<ul id="statement_format">
</ul>
<p>You can install more file formats by <a style="cursor: pointer" type="action" name="account_bank_statement_import.install_more_import_formats_action">installing the related modules</a></p>
<field name="data_file" filename="filename" placeholder="Choose a file to import..."/>
<field name="filename" invisible="1"/>
<footer>
@@ -22,6 +23,14 @@
</field>
</record>
<record id="install_more_import_formats_action" model="ir.actions.act_window">
<field name="name">Install Import Format</field>
<field name="res_model">ir.module.module</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context" eval="{'search_default_name': 'account_bank_statement_import'}"/>
<field name="search_view_id" ref="base.view_module_filter"/>
</record>
<record id="action_account_bank_statement_import" model="ir.actions.act_window">
<field name="name">Import</field>
<field name="type">ir.actions.act_window</field>
@@ -37,13 +46,8 @@
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.account_journal_dashboard_kanban_view"/>
<field name="arch" type="xml">
<xpath expr='//div[@name="bank_journal_default_cta"]' position='inside'>
<br t-if="dashboard.number_to_reconcile == 0"/><button t-if="dashboard.number_to_reconcile == 0" type="object" name="import_statement" class="btn btn-primary btn-sm">Import Statement</button>
<a t-if="dashboard.number_to_reconcile > 0" type="object" name="import_statement" class="oe_inline">Import Statement</a>
</xpath>
<xpath expr='//div[@name="bank_journal_cta"]' position='inside'>
<button t-if="dashboard.bank_statements_source == 'file_import' &amp;&amp; dashboard.number_to_reconcile == 0" type="object" name="import_statement" class="btn btn-primary btn-sm">Import Statement</button>
<a t-if="dashboard.bank_statements_source == 'file_import' &amp;&amp; dashboard.number_to_reconcile > 0" type="object" name="import_statement" class="oe_inline">Import Statement</a>
<xpath expr='//span[@name="button_import_placeholder"]' position='inside'>
<span>or <a type="object" name="import_statement">Import</a></span>
</xpath>
<xpath expr='//div[@name="bank_cash_commands"]' position="before">
<t t-if="journal_type == 'bank'">
@@ -1,12 +1,24 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo import models, api, _
class AccountJournal(models.Model):
_inherit = "account.journal"
bank_statements_source = fields.Selection(selection_add=[("file_import", "File Import")])
def _get_bank_statements_available_import_formats(self):
""" Returns a list of strings representing the supported import formats.
"""
return []
def __get_bank_statements_available_sources(self):
rslt = super(AccountJournal, self).__get_bank_statements_available_sources()
formats_list = self._get_bank_statements_available_import_formats()
if formats_list:
formats_list.sort()
import_formats_str = ', '.join(formats_list)
rslt.append(("file_import", _("Import") + "(" + import_formats_str + ")"))
return rslt
@api.multi
def import_statement(self):
@@ -1 +1,2 @@
from . import journal_creation
from . import setup_wizards
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class SetupBarBankConfigWizard(models.TransientModel):
_inherit = 'account.setup.bank.manual.config'
def validate(self):
""" Default the bank statement source of new bank journals as 'file_import'
"""
super(SetupBarBankConfigWizard, self).validate()
if self.create_or_link_option == 'new':
self.linked_journal_id.bank_statements_source = 'file_import'
+10 -7
View File
@@ -48,16 +48,19 @@ def validate_iban(iban):
class ResPartnerBank(models.Model):
_inherit = "res.partner.bank"
acc_type = fields.Selection(selection_add=[("iban", "IBAN")])
@api.model
def _get_supported_account_types(self):
rslt = super(ResPartnerBank, self)._get_supported_account_types()
rslt.append(('iban', _('IBAN')))
return rslt
@api.one
@api.depends('acc_number')
def _compute_acc_type(self):
@api.model
def retrieve_acc_type(self, acc_number):
try:
validate_iban(self.acc_number)
self.acc_type = 'iban'
validate_iban(acc_number)
return 'iban'
except ValidationError:
super(ResPartnerBank, self)._compute_acc_type()
return super(ResPartnerBank, self).retrieve_acc_type(acc_number)
def get_bban(self):
if self.acc_type != 'iban':
@@ -10,7 +10,7 @@
<div class="row">
<div class="col-xs-8">
<a type="object" t-if="journal_type == 'purchase'" name="open_expenses_action">
<t t-esc="dashboard.number_expenses_to_pay"/> Expense Reports to Process
<t t-esc="dashboard.number_expenses_to_pay"/> Expenses to Process
</a>
</div>
<div class="col-xs-4 text-right">
+13 -9
View File
@@ -3,7 +3,7 @@
import re
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.tools.misc import mod10r
@@ -28,19 +28,23 @@ class ResBank(models.Model):
class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank'
acc_type = fields.Selection(selection_add=[("postal", "Postal")])
l10n_ch_postal = fields.Char(string='ISR reference', help='The ISR number of the company within the bank')
@api.depends('acc_number')
def _compute_acc_type(self):
@api.model
def _get_supported_account_types(self):
rslt = super(ResPartnerBank, self)._get_supported_account_types()
rslt.append(('postal', _('Postal')))
return rslt
@api.model
def retrieve_acc_type(self, acc_number):
""" Overridden method enabling the recognition of swiss postal bank
account numbers.
"""
for record in self:
if _is_l10n_ch_postal(record.acc_number):
record.acc_type = 'postal'
else:
super(ResPartnerBank, record)._compute_acc_type()
if _is_l10n_ch_postal(acc_number):
return 'postal'
else:
return super(ResPartnerBank, self).retrieve_acc_type(acc_number)
@api.onchange('acc_number')
def _onchange_set_l10n_ch_postal(self):
+18 -4
View File
@@ -4,7 +4,7 @@ import re
import collections
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.tools import pycompat
@@ -59,7 +59,15 @@ class ResPartnerBank(models.Model):
_description = 'Bank Accounts'
_order = 'sequence'
acc_type = fields.Selection([('bank', 'Normal')], compute='_compute_acc_type', string='Type', help='Bank account type: Normal or IBAN. Inferred from the bank account number.')
@api.model
def get_supported_account_types(self):
return self._get_supported_account_types()
@api.model
def _get_supported_account_types(self):
return [('bank', _('Normal'))]
acc_type = fields.Selection(selection=lambda x: x.env['res.partner.bank'].get_supported_account_types(), compute='_compute_acc_type', string='Type', help='Bank account type: Normal or IBAN. Inferred from the bank account number.')
acc_number = fields.Char('Account Number', required=True)
sanitized_acc_number = fields.Char(compute='_compute_sanitized_acc_number', string='Sanitized Account Number', readonly=True, store=True)
acc_holder_name = fields.Char(string='Account Holder Name', help="Account holder name, in case it is different than the name of the Account Holder")
@@ -80,10 +88,16 @@ class ResPartnerBank(models.Model):
for bank in self:
bank.sanitized_acc_number = sanitize_account_number(bank.acc_number)
@api.multi
@api.depends('acc_number')
def _compute_acc_type(self):
for bank in self:
bank.acc_type = 'bank'
bank.acc_type = self.retrieve_acc_type(bank.acc_number)
@api.model
def retrieve_acc_type(self, acc_number):
""" To be overridden by subclasses in order to support other account_types.
"""
return 'bank'
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
@@ -150,14 +150,14 @@ tour.register('main_flow_tour', {
extra_trigger: "body:not(:has(table.o_list_view div.o_field_widget[name='product_id'] input))",
content: _t("Click here to add some lines."),
position: "bottom",
},
{
},
{
trigger: ".o_selected_row .o_required_modifier[name=product_id] input",
extra_trigger: '.o_field_widget[name=bom_line_ids] .o_data_row:nth(1).o_selected_row',
content: _t("Select a product, or create a new one on the fly."),
position: "right",
run: "text the_flow.component2",
},
},
{
trigger: ".ui-menu-item > a:contains('the_flow.component2')",
auto: true,
@@ -640,7 +640,44 @@ tour.register('main_flow_tour', {
position: 'bottom',
}, {
edition: "enterprise",
trigger: 'div[name=bank_journal_default_cta] > a[data-name=create_bank_statement], div[name=bank_journal_cta] > a[data-name=create_bank_statement]',
trigger: 'div[name=bank_journal_cta] > button[data-name=action_cofigure_bank_journal], div[name=bank_journal_cta] > button[data-name=action_configure_bank_journal]',
content: _t('Configure Bank Journal'),
position: 'bottom',
}, {
edition: "enterprise",
trigger: '.js_configure_manually',
content: _t('Enter manual data for bank account'),
position: 'bottom',
}, {
edition: "enterprise",
trigger: '.o_field_widget[name=setup_journal_link_creation] .o_radio_input[data-value="link"]',
content: _t('Link to existing journal'),
position: 'bottom',
}, {
edition: "enterprise",
trigger: ".o_field_widget[name=acc_number]",
content: _t("Enter an account number"),
position: "right",
run: "text 867656544",
}, {
edition: "enterprise",
trigger: ".o_field_widget[name=single_journal_id] input",
extra_trigger: ".modal-dialog",
content: _t("Select Journal"),
position: "bottom",
run: 'text Bank',
}, {
edition: "enterprise",
trigger: ".ui-menu-item > a",
auto: true,
in_modal:false,
}, {
trigger: ".modal-footer .btn-primary",
content: _t('Save'),
position: 'bottom',
}, {
edition: "enterprise",
trigger: 'div[name=bank_journal_cta] > a[data-name=create_bank_statement], div[name=bank_journal_cta] > a[data-name=create_bank_statement]',
content: _t('Create a new bank statement'),
position: 'bottom',
}, {