diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 760bec54..da2896af 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -45,6 +45,7 @@ class AccountAccountTag(models.Model): class AccountAccount(models.Model): _name = "account.account" _description = "Account" + _inherit = ['ir.branch.company.mixin'] _order = "code" @api.multi @@ -84,6 +85,17 @@ class AccountAccount(models.Model): ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !') ] + @api.multi + @api.constrains('branch_id', 'company_id') + def _check_company_branch(self): + for record in self: + if record.branch_id and record.company_id != record.branch_id.company_id: + raise ValidationError(_( + 'Configuration Error of Company:\n' + 'The Account Company (%s) and the Company (%s) of ' + 'Branch must be the same!') % ( + record.company_id.name, record.branch_id.company_id.name)) + def _compute_opening_debit_credit(self): for record in self: opening_debit = opening_credit = 0.0 @@ -305,6 +317,7 @@ class AccountGroup(models.Model): class AccountJournal(models.Model): _name = "account.journal" + _inherit = ['ir.branch.company.mixin'] _description = "Journal" _order = 'sequence, type, code' @@ -387,6 +400,17 @@ class AccountJournal(models.Model): ('code_company_uniq', 'unique (code, name, company_id)', 'The code and name of the journal must be unique per company !'), ] + @api.multi + @api.constrains('branch_id', 'company_id') + def _check_company_branch(self): + for record in self: + if record.branch_id and record.company_id != record.branch_id.company_id: + raise ValidationError(_( + 'Configuration Error of Company:\n' + 'The Account Company (%s) and the Company (%s) of ' + 'Branch must be the same!') % ( + record.company_id.name, record.branch_id.company_id.name)) + @api.multi # do not depend on 'sequence_id.date_range_ids', because # sequence_id._get_current_sequence() may invalidate it! @@ -720,6 +744,7 @@ class AccountTaxGroup(models.Model): class AccountTax(models.Model): _name = 'account.tax' + _inherit = ['ir.branch.company.mixin'] _description = 'Tax' _order = 'sequence,id' @@ -1008,6 +1033,7 @@ class AccountTax(models.Model): class AccountReconcileModel(models.Model): _name = "account.reconcile.model" + _inherit = ['ir.branch.company.mixin'] _description = "Preset to create journal entries during a invoices and payments matching" name = fields.Char(string='Button Label', required=True) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index a1219c5a..acd22989 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -144,6 +144,8 @@ class AccountBankStatement(models.Model): journal_type = fields.Selection(related='journal_id.type', help="Technical field used for usability purposes") company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', store=True, readonly=True, default=lambda self: self.env['res.company']._company_default_get('account.bank.statement')) + branch_id = fields.Many2one(related='journal_id.branch_id', + string='Branch', store=True, readonly=True) total_entry_encoding = fields.Monetary('Transactions Subtotal', compute='_end_balance', store=True, help="Total of transaction lines.") balance_end = fields.Monetary('Computed Balance', compute='_end_balance', store=True, help='Balance as calculated based on Opening Balance and transaction lines') @@ -366,6 +368,8 @@ class AccountBankStatementLine(models.Model): note = fields.Text(string='Notes') sequence = fields.Integer(index=True, help="Gives the sequence order when displaying a list of bank statement lines.", default=1) company_id = fields.Many2one('res.company', related='statement_id.company_id', string='Company', store=True, readonly=True) + branch_id = fields.Many2one(related='statement_id.branch_id', + string='Company', store=True, readonly=True) journal_entry_ids = fields.One2many('account.move.line', 'statement_line_id', 'Journal Items', copy=False, readonly=True) amount_currency = fields.Monetary(help="The amount expressed in an optional other currency if it is a multi-currency entry.") currency_id = fields.Many2one('res.currency', string='Currency', help="The optional other currency if it is a multi-currency entry.") diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 0213a3ca..5da26b99 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -41,7 +41,8 @@ MAGIC_COLUMNS = ('id', 'create_uid', 'create_date', 'write_uid', 'write_date') class AccountInvoice(models.Model): _name = "account.invoice" - _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin', + 'ir.branch.company.mixin'] _description = "Invoice" _order = "date_invoice desc, number desc, id desc" @@ -383,6 +384,18 @@ class AccountInvoice(models.Model): domain += [('journal_id', '=', self.journal_id.id), ('state', 'not in', ['draft', 'cancel'])] return journal_sequence, domain + @api.constrains('company_id', 'branch_id') + def _check_company(self): + for order in self: + if order.branch_id and order.company_id != order.branch_id.company_id: + raise ValidationError( + _('Configuration Error of Company:\n' + 'The Invoice Company (%s) and ' + 'the Company (%s) of Branch must ' + 'be the same company!') % (order.company_id.name, + order.branch_id.company_id.name) + ) + def _compute_portal_url(self): super(AccountInvoice, self)._compute_portal_url() for order in self: @@ -1131,6 +1144,7 @@ class AccountInvoice(models.Model): date = inv.date or inv.date_invoice move_vals = { 'ref': inv.reference, + 'branch_id': inv.branch_id and inv.branch_id.id, 'line_ids': line, 'journal_id': journal.id, 'date': date, @@ -1493,6 +1507,9 @@ class AccountInvoiceLine(models.Model): analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') company_id = fields.Many2one('res.company', string='Company', related='invoice_id.company_id', store=True, readonly=True, related_sudo=False) + branch_id = fields.Many2one(string='Company', + related='invoice_id.branch_id', store=True, + readonly=True, related_sudo=False) partner_id = fields.Many2one('res.partner', string='Partner', related='invoice_id.partner_id', store=True, readonly=True, related_sudo=False) currency_id = fields.Many2one('res.currency', related='invoice_id.currency_id', store=True, related_sudo=False) @@ -1676,6 +1693,9 @@ class AccountInvoiceTax(models.Model): manual = fields.Boolean(default=True) sequence = fields.Integer(help="Gives the sequence order when displaying a list of invoice tax.") company_id = fields.Many2one('res.company', string='Company', related='account_id.company_id', store=True, readonly=True) + branch_id = fields.Many2one(string='Branch', + related='account_id.branch_id', store=True, + readonly=True) currency_id = fields.Many2one('res.currency', related='invoice_id.currency_id', store=True, readonly=True) base = fields.Monetary(string='Base', compute='_compute_base_amount', store=True) @@ -1687,6 +1707,7 @@ class AccountInvoiceTax(models.Model): class AccountPaymentTerm(models.Model): _name = "account.payment.term" + _inherit = ['ir.branch.company.mixin'] _description = "Payment Terms" _order = "sequence, id" diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 18108c93..f2f618d6 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -19,6 +19,7 @@ class AccountMove(models.Model): _name = "account.move" _description = "Account Entry" _order = 'date desc, id desc' + _inherit = ['ir.branch.company.mixin'] @api.multi @api.depends('name', 'state') @@ -445,6 +446,8 @@ class AccountMoveLine(models.Model): analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account') analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic tags') company_id = fields.Many2one('res.company', related='account_id.company_id', string='Company', store=True) + branch_id = fields.Many2one(related='move_id.branch_id', string='Branch', + store=True) counterpart = fields.Char("Counterpart", compute='_get_counterpart', help="Compute the counter part accounts of this journal item for this journal entry. This can be needed in reports.") # TODO: put the invoice link and partner_id on the account_move @@ -463,6 +466,31 @@ class AccountMoveLine(models.Model): ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'), ] + @api.constrains('move_id', 'branch_id') + def _check_branch(self): + for order in self: + move_branch_id = order.move_id.branch_id + if order.branch_id and move_branch_id != order.branch_id: + raise ValidationError( + _('Configuration Error of Branch:\n' + 'The Move Line Branch (%s) and ' + 'the Branch (%s) of Journal Entry must ' + 'be the same branch!') % (order.branch_id.name, + move_branch_id.name) + ) + + @api.constrains('company_id', 'branch_id') + def _check_company(self): + for order in self: + if order.branch_id and order.company_id != order.branch_id.company_id: + raise ValidationError( + _('Configuration Error of Company:\n' + 'The Move Line Company (%s) and ' + 'the Company (%s) of Branch must ' + 'be the same company!') % (order.company_id.name, + order.branch_id.company_id.name) + ) + @api.model def default_get(self, fields): rec = super(AccountMoveLine, self).default_get(fields) @@ -1446,6 +1474,7 @@ class AccountMoveLine(models.Model): return { 'name': self.name, 'date': self.date, + 'branch_id': self.branch_id and self.branch_id.id, 'account_id': self.analytic_account_id.id, 'tag_ids': [(6, 0, self.analytic_tag_ids.ids)], 'unit_amount': self.quantity, @@ -1538,6 +1567,8 @@ class AccountPartialReconcile(models.Model): company_currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True, help='Utility field to express amount currency') company_id = fields.Many2one('res.company', related='debit_move_id.company_id', store=True, string='Currency') + branch_id = fields.Many2one(related='debit_move_id.branch_id', store=True, + string='Branch') full_reconcile_id = fields.Many2one('account.full.reconcile', string="Full Reconcile", copy=False) max_date = fields.Date(string='Max Date of Matched Lines', compute='_compute_max_date', readonly=True, copy=False, store=True, diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index e66f45a5..73ed3d4a 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -49,6 +49,8 @@ class account_abstract_payment(models.AbstractModel): communication = fields.Char(string='Memo') journal_id = fields.Many2one('account.journal', string='Payment Journal', required=True, domain=[('type', 'in', ('bank', 'cash'))]) company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', readonly=True) + branch_id = fields.Many2one(related='journal_id.branch_id', + string='Branch', readonly=True) hide_payment_method = fields.Boolean(compute='_compute_hide_payment_method', help="Technical field used to hide the payment method if the selected journal has only one available which is 'manual'") @@ -238,7 +240,7 @@ class account_register_payments(models.TransientModel): class account_payment(models.Model): _name = "account.payment" - _inherit = ['mail.thread', 'account.abstract.payment'] + _inherit = ['mail.thread', 'account.abstract.payment', 'ir.branch.company.mixin'] _description = "Payments" _order = "payment_date desc, name desc" diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py index 888c0d2f..f1e3bc07 100644 --- a/addons/account/models/chart_template.py +++ b/addons/account/models/chart_template.py @@ -57,6 +57,7 @@ def preserve_existing_tags_on_taxes(cr, registry, module): class AccountAccountTemplate(models.Model): _name = "account.account.template" _description = 'Templates for Accounts' + _inherit = ['ir.branch.company.mixin'] _order = "code" name = fields.Char(required=True, index=True) @@ -498,6 +499,7 @@ class AccountChartTemplate(models.Model): class AccountTaxTemplate(models.Model): _name = 'account.tax.template' + _inherit = ['ir.branch.company.mixin'] _description = 'Templates for Taxes' _order = 'id' @@ -621,6 +623,7 @@ class AccountTaxTemplate(models.Model): class AccountFiscalPositionTemplate(models.Model): _name = 'account.fiscal.position.template' + _inherit = ['ir.branch.company.mixin'] _description = 'Template for Fiscal Position' sequence = fields.Integer() @@ -642,6 +645,7 @@ class AccountFiscalPositionTemplate(models.Model): class AccountFiscalPositionTaxTemplate(models.Model): _name = 'account.fiscal.position.tax.template' + _inherit = ['ir.branch.company.mixin'] _description = 'Template Tax Fiscal Position' _rec_name = 'position_id' @@ -653,6 +657,7 @@ class AccountFiscalPositionTaxTemplate(models.Model): class AccountFiscalPositionAccountTemplate(models.Model): _name = 'account.fiscal.position.account.template' _description = 'Template Account Fiscal Mapping' + _inherit = ['ir.branch.company.mixin'] _rec_name = 'position_id' position_id = fields.Many2one('account.fiscal.position.template', string='Fiscal Mapping', required=True, ondelete='cascade') @@ -961,6 +966,7 @@ class AccountBankAccountsWizard(models.TransientModel): class AccountReconcileModelTemplate(models.Model): _name = "account.reconcile.model.template" + _inherit = ['ir.branch.company.mixin'] chart_template_id = fields.Many2one('account.chart.template', string='Chart Template', required=True) name = fields.Char(string='Button Label', required=True) diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index b8108fc9..e234bf7e 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -11,6 +11,7 @@ from flectra.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP class AccountFiscalPosition(models.Model): _name = 'account.fiscal.position' + _inherit = ['ir.branch.company.mixin'] _description = 'Fiscal Position' _order = 'sequence' @@ -164,6 +165,7 @@ class AccountFiscalPosition(models.Model): class AccountFiscalPositionTax(models.Model): _name = 'account.fiscal.position.tax' + _inherit = ['ir.branch.company.mixin'] _description = 'Taxes Fiscal Position' _rec_name = 'position_id' @@ -182,6 +184,7 @@ class AccountFiscalPositionTax(models.Model): class AccountFiscalPositionAccount(models.Model): _name = 'account.fiscal.position.account' _description = 'Accounts Fiscal Position' + _inherit = ['ir.branch.company.mixin'] _rec_name = 'position_id' position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position', diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index c85bd70c..8353516b 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -29,6 +29,10 @@ class ReportAgedPartnerBalance(models.AbstractModel): cr = self.env.cr user_company = self.env.user.company_id.id move_state = ['draft', 'posted'] + branch_id = data['form'].get('branch_id', False) + branch = '' + if branch_id: + branch = 'AND (l.branch_id =' + str(branch_id[0]) + ')' if target_move == 'posted': move_state = ['posted'] arg_list = (tuple(move_state), tuple(account_type)) @@ -49,7 +53,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): AND (l.move_id = am.id) AND (am.state IN %s) AND (account_account.internal_type IN %s) - AND ''' + reconciliation_clause + ''' + AND ''' + reconciliation_clause + branch +''' AND (l.date <= %s) AND l.company_id = %s ORDER BY UPPER(res_partner.name)''' @@ -75,7 +79,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): AND (account_account.internal_type IN %s) AND (COALESCE(l.date_maturity,l.date) > %s)\ AND ((l.partner_id IN %s) OR (l.partner_id IS NULL)) - AND (l.date <= %s) + AND (l.date <= %s) ''' + branch + ''' AND l.company_id = %s''' cr.execute(query, (tuple(move_state), tuple(account_type), date_from, tuple(partner_ids), date_from, user_company)) aml_ids = cr.fetchall() @@ -125,7 +129,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): AND (am.state IN %s) AND (account_account.internal_type IN %s) AND ((l.partner_id IN %s) OR (l.partner_id IS NULL)) - AND ''' + dates_query + ''' + AND ''' + dates_query + branch +''' AND (l.date <= %s) AND l.company_id = %s''' cr.execute(query, args_list) diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index 9799ba80..d70d9346 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -6,6 +6,7 @@ from flectra import models, fields, api class AccountInvoiceReport(models.Model): _name = "account.invoice.report" + _inherit = ['ir.branch.company.mixin'] _description = "Invoices Statistics" _auto = False _rec_name = 'date' @@ -72,7 +73,7 @@ class AccountInvoiceReport(models.Model): _depends = { 'account.invoice': [ - 'account_id', 'amount_total_company_signed', 'commercial_partner_id', 'company_id', + 'account_id', 'amount_total_company_signed', 'commercial_partner_id', 'company_id', 'branch_id', 'currency_id', 'date_due', 'date_invoice', 'fiscal_position_id', 'journal_id', 'partner_bank_id', 'partner_id', 'payment_term_id', 'residual', 'state', 'type', 'user_id', @@ -92,7 +93,7 @@ class AccountInvoiceReport(models.Model): select_str = """ SELECT sub.id, sub.date, sub.product_id, sub.partner_id, sub.country_id, sub.account_analytic_id, sub.payment_term_id, sub.uom_name, sub.currency_id, sub.journal_id, - sub.fiscal_position_id, sub.user_id, sub.company_id, sub.nbr, sub.type, sub.state, + sub.fiscal_position_id, sub.user_id, sub.company_id, sub.branch_id, sub.nbr, sub.type, sub.state, sub.categ_id, sub.date_due, sub.account_id, sub.account_line_id, sub.partner_bank_id, sub.product_qty, sub.price_total as price_total, sub.price_average as price_average, COALESCE(cr.rate, 1) as currency_rate, sub.residual as residual, sub.commercial_partner_id as commercial_partner_id @@ -105,7 +106,7 @@ class AccountInvoiceReport(models.Model): ai.date_invoice AS date, ail.product_id, ai.partner_id, ai.payment_term_id, ail.account_analytic_id, u2.name AS uom_name, - ai.currency_id, ai.journal_id, ai.fiscal_position_id, ai.user_id, ai.company_id, + ai.currency_id, ai.journal_id, ai.fiscal_position_id, ai.user_id, ai.company_id, ai.branch_id, 1 AS nbr, ai.type, ai.state, pt.categ_id, ai.date_due, ai.account_id, ail.account_id AS account_line_id, ai.partner_bank_id, @@ -148,7 +149,7 @@ class AccountInvoiceReport(models.Model): group_by_str = """ GROUP BY ail.id, ail.product_id, ail.account_analytic_id, ai.date_invoice, ai.id, ai.partner_id, ai.payment_term_id, u2.name, u2.id, ai.currency_id, ai.journal_id, - ai.fiscal_position_id, ai.user_id, ai.company_id, ai.type, invoice_type.sign, ai.state, pt.categ_id, + ai.fiscal_position_id, ai.user_id, ai.company_id, ai.branch_id, ai.type, invoice_type.sign, ai.state, pt.categ_id, ai.date_due, ai.account_id, ail.account_id, ai.partner_bank_id, ai.residual_company_signed, ai.amount_total_company_signed, ai.commercial_partner_id, partner.country_id """ diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml index abacf2db..89009e9d 100644 --- a/addons/account/security/account_security.xml +++ b/addons/account/security/account_security.xml @@ -44,6 +44,73 @@ + + + + Account Entry Multi Branch + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Entry lines Multi Branch + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Invoice multi-branch + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Invoice Analysis multi-branch + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Invoice Line branch rule + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Account bank statement branch rule + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Account bank statement line branch rule + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Account reconcile model template company rule + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Account payment company rule + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Account Entry diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py index 8d6fb294..71f06a2c 100644 --- a/addons/account/tests/__init__.py +++ b/addons/account/tests/__init__.py @@ -18,3 +18,8 @@ from . import test_search from . import test_setup_bar from . import test_tax from . import test_templates_consistency +from . import test_account_branch +from . import test_invoice_branch +from . import test_journal_entries_branch +from . import test_branch_moves +from . import test_payment_branch diff --git a/addons/account/tests/test_account_branch.py b/addons/account/tests/test_account_branch.py new file mode 100644 index 00000000..cccbd696 --- /dev/null +++ b/addons/account/tests/test_account_branch.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +from flectra.addons.account.tests.account_test_classes import AccountingTestCase + + +class TestAccountBranch(AccountingTestCase): + def setUp(self): + super(TestAccountBranch, self).setUp() + self.apple_product = self.env.ref('product.product_product_7') + self.keyboard_product = self.env.ref('product.product_product_9') + self.ipod_product = self.env.ref('product.product_product_11') + self.asset_account = self.env.ref('l10n_generic_coa.conf_stk') + self.model_account_journal = self.env['account.journal'] + self.model_account = self.env['account.account'] + self.main_company = self.env.ref('base.main_company') + self.manager_group = self.env.ref('account.group_account_manager') + self.model_user = self.env['res.users'] + self.model_account_invoice = self.env['account.invoice'] + self.account_partner = self.env.ref('base.res_partner_1') + self.branch_1 = self.env.ref('base_branch_company.data_branch_1') + self.branch_2 = self.env.ref('base_branch_company.data_branch_2') + self.branch_3 = self.env.ref('base_branch_company.data_branch_3') + user_type = self.env.ref('account.data_account_type_liquidity') + self.account_type = self.env.ref('account.data_account_type_expenses') + + self.user_id = self.model_user.with_context( + {'no_reset_password': True}).create({ + 'company_id': self.main_company.id, + 'branch_ids': [(4, self.branch_2.id), (4, self.branch_3.id)], + 'company_ids': [(4, self.main_company.id)], + 'groups_id': [(6, 0, [self.manager_group.id])], + 'name': 'Test User 1', + 'email': 'demo@yourcompany.com', + 'password': '123', + 'login': 'tes_user_1', + }) + + self.user_2 = self.model_user.with_context({ + 'no_reset_password': True}).create({ + 'company_id': self.main_company.id, + 'branch_ids': [(4, self.branch_3.id)], + 'company_ids': [(4, self.main_company.id)], + 'groups_id': [(6, 0, [self.manager_group.id])], + 'name': 'Test User', + 'email': 'demo@yourcompany.com', + 'password': '123', + 'login': 'test_user_2', + }) + + self.cash_account = self.model_account.create({ + 'company_id': self.main_company.id, + 'user_type_id': user_type.id, + 'code': 'cash_test', + 'name': 'Test Cash Account', + }) + + self.cash_journal = self.model_account_journal.create({ + 'company_id': self.main_company.id, + 'branch_id': self.branch_1.id, + 'name': 'Cash Journal - Branch 1', + 'default_credit_account_id': self.cash_account.id, + 'default_debit_account_id': self.cash_account.id, + 'type': 'cash', + 'code': 'cash_branch_1', + }) + + def invoice_values(self, branch_id): + products = [(self.apple_product, 1000), + (self.keyboard_product, 500), + (self.ipod_product, 800)] + lines_data = [] + account_id = self.model_account.search([ + ('user_type_id', '=', self.account_type.id)], limit=1).id + for product_id, quantity in products: + values = { + 'product_id': product_id.id, + 'name': product_id.name, + 'price_unit': 120, + 'quantity': quantity, + 'account_id': account_id + } + lines_data.append((0, 0, values)) + vals = { + 'partner_id': self.account_partner.id, + 'type': 'in_invoice', + 'name': "Supplier Invoice", + # 'reference_type': "none", + 'account_id': self.account_partner.property_account_payable_id.id, + 'invoice_line_ids': lines_data, + 'branch_id': branch_id, + } + return vals diff --git a/addons/account/tests/test_branch_moves.py b/addons/account/tests/test_branch_moves.py new file mode 100644 index 00000000..e8b22c1d --- /dev/null +++ b/addons/account/tests/test_branch_moves.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +from . import test_account_branch + + +class TestBranchJournalEntries(test_account_branch.TestAccountBranch): + + def test_branch_security_move_line(self): + move_ids = self.env['account.move.line'].sudo(self.user_2.id).\ + search([('branch_id', '=', self.branch_2.id)]) + self.assertFalse(move_ids, 'USer 2 should not have access to move lines with Branch %s' + % self.branch_2.name) diff --git a/addons/account/tests/test_invoice_branch.py b/addons/account/tests/test_invoice_branch.py new file mode 100644 index 00000000..6febaa8b --- /dev/null +++ b/addons/account/tests/test_invoice_branch.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from . import test_account_branch + + +class TestInvoiceBranch(test_account_branch.TestAccountBranch): + + def test_invoice_create(self): + self.invoice_id = self.model_account_invoice.sudo(self.user_id.id).create(self.invoice_values(self.branch_2.id)) + + invoices = self.model_account_invoice.sudo(self.user_2.id).search([('branch_id', '=', self.branch_2.id)]) + + self.assertFalse(invoices, 'USer 2 should not have access to Invoice with Branch %s' + % self.branch_2.name) + + self.invoice_id.sudo(self.user_id.id).action_invoice_open() + all_branch = all(move_line_id.branch_id.id == self.branch_2.id for + move_line_id in self.invoice_id.move_id.line_ids) + self.assertNotEqual(all_branch, False, 'Journal Entries have different Branch.') diff --git a/addons/account/tests/test_journal_entries_branch.py b/addons/account/tests/test_journal_entries_branch.py new file mode 100644 index 00000000..f73de1e1 --- /dev/null +++ b/addons/account/tests/test_journal_entries_branch.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +from . import test_account_branch + + +class TestJournalEntryBranch(test_account_branch.TestAccountBranch): + + def test_journal_entries_branch(self): + journal_ids = self.model_account_journal.search([('code', '=', 'MISC')], + limit=1) + move_vals = self.env['account.move'].default_get([]) + lines = [ + (0, 0, { + 'name': 'Test', + 'account_id': self.asset_account.id, + 'debit': 0, + 'credit': 100, + 'branch_id': self.branch_1.id, + }), + (0, 0, { + 'name': 'Test', + 'account_id': self.asset_account.id, + 'debit': 100, + 'credit': 0, + 'branch_id': self.branch_1.id, + }) + ] + move_vals.update({ + 'journal_id': journal_ids and journal_ids.id, + 'line_ids': lines, + }) + move = self.env['account.move'].sudo(self.user_id.id).create(move_vals) + move.post() + + def _check_balance(self, account_id, acc_type='clearing'): + domain = [('account_id', '=', account_id)] + balance = self._get_balance(domain) + self.assertEqual(balance, 0.0, 'Balance is 0 for all Branch.') + domain = [('account_id', '=', account_id), + ('branch_id', '=', self.branch_2.id)] + balance = self._get_balance(domain) + if acc_type == 'other': + self.assertEqual(balance, -100, + 'Balance is -100 for Branch.') + else: + self.assertEqual(balance, 100, + 'Balance is 100 for Branch.') + domain = [('account_id', '=', account_id), + ('branch_id', '=', self.branch_3.id)] + balance = self._get_balance(domain) + if acc_type == 'other': + self.assertEqual(balance, 100.0, + 'Balance is 100 for Branch') + else: + self.assertEqual(balance, -100.0, + 'Balance is -100 for Branch') + + def _get_balance(self, domain): + + aml_rec = self.env['account.move.line'].sudo(self.user_id.id).read_group(domain,['debit', 'credit', 'account_id'], ['account_id']) + if aml_rec: + aml_rec = aml_rec[0] + a = aml_rec.get('debit', 0) - aml_rec.get('credit', 0) + return a diff --git a/addons/account/tests/test_payment_branch.py b/addons/account/tests/test_payment_branch.py new file mode 100644 index 00000000..9135bae7 --- /dev/null +++ b/addons/account/tests/test_payment_branch.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from flectra.addons.account.tests import test_account_branch +import time + + +class TestPaymentsBranch(test_account_branch.TestAccountBranch): + + def test_payment_branch(self): + self.invoice_id = self.model_account_invoice.sudo(self.user_id.id).create( + self.invoice_values(self.branch_2.id)) + self.invoice_id.sudo(self.user_id.id).action_invoice_open() + + context = {'active_ids': [self.invoice_id.id], 'active_model': 'account.invoice'} + create_payments = self.env['account.register.payments'].sudo(self.user_id.id).with_context(context).create({ + 'payment_method_id': self.env.ref("account.account_payment_method_manual_in").id, + 'journal_id': self.cash_journal.id, + 'payment_date': time.strftime('%Y') + '-12-17', + + }) + create_payments.create_payments() + payment = self.env['account.payment'].sudo(self.user_2.id).search([('branch_id', '=', self.branch_2.id)]) + self.assertFalse(payment, 'USer 2 should not have access to Payments with Branch %s' + % self.branch_2.name) diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index cbe8e603..8c246d08 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -330,6 +330,7 @@ + @@ -477,6 +478,7 @@ + diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 535cbb56..e6348f23 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1155,6 +1155,7 @@ + @@ -1227,6 +1228,7 @@ + @@ -1304,6 +1306,7 @@ + @@ -1371,6 +1374,7 @@ + @@ -1452,6 +1456,7 @@ + @@ -1465,6 +1470,7 @@ + diff --git a/addons/account/views/report_generalledger.xml b/addons/account/views/report_generalledger.xml index 81566721..c612aec5 100644 --- a/addons/account/views/report_generalledger.xml +++ b/addons/account/views/report_generalledger.xml @@ -40,6 +40,10 @@ Date from :
Date to : +
+ Branch: +

+
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index e4d809fb..c7178ddd 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -48,6 +48,10 @@ Reference:

+

+ Branch: +

+

diff --git a/addons/account/views/report_partnerledger.xml b/addons/account/views/report_partnerledger.xml index 782fc44d..5482d71f 100644 --- a/addons/account/views/report_partnerledger.xml +++ b/addons/account/views/report_partnerledger.xml @@ -24,6 +24,10 @@

All Entries

All Posted Entries

+
+ Branch: +

+
diff --git a/addons/account/wizard/account_report_aged_partner_balance_view.xml b/addons/account/wizard/account_report_aged_partner_balance_view.xml index 6791dc45..c1653124 100644 --- a/addons/account/wizard/account_report_aged_partner_balance_view.xml +++ b/addons/account/wizard/account_report_aged_partner_balance_view.xml @@ -12,6 +12,8 @@ + + diff --git a/addons/account/wizard/account_report_common.py b/addons/account/wizard/account_report_common.py index 9712be6b..a74cb513 100644 --- a/addons/account/wizard/account_report_common.py +++ b/addons/account/wizard/account_report_common.py @@ -6,6 +6,7 @@ from flectra import api, fields, models, _ class AccountCommonReport(models.TransientModel): _name = "account.common.report" _description = "Account Common Report" + _inherit = ['ir.branch.company.mixin'] company_id = fields.Many2one('res.company', string='Company', readonly=True, default=lambda self: self.env.user.company_id) journal_ids = fields.Many2many('account.journal', string='Journals', required=True, default=lambda self: self.env['account.journal'].search([])) @@ -21,6 +22,7 @@ class AccountCommonReport(models.TransientModel): result['state'] = 'target_move' in data['form'] and data['form']['target_move'] or '' result['date_from'] = data['form']['date_from'] or False result['date_to'] = data['form']['date_to'] or False + result['branch_id'] = data['form']['branch_id'] or False result['strict_range'] = True if result['date_from'] else False return result @@ -33,7 +35,7 @@ class AccountCommonReport(models.TransientModel): data = {} data['ids'] = self.env.context.get('active_ids', []) data['model'] = self.env.context.get('active_model', 'ir.ui.menu') - data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move'])[0] + data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move', 'branch_id'])[0] used_context = self._build_contexts(data) data['form']['used_context'] = dict(used_context, lang=self.env.context.get('lang') or 'en_US') return self._print_report(data) diff --git a/addons/account/wizard/account_report_common_view.xml b/addons/account/wizard/account_report_common_view.xml index 5c8c9fdd..13b137a3 100644 --- a/addons/account/wizard/account_report_common_view.xml +++ b/addons/account/wizard/account_report_common_view.xml @@ -12,6 +12,12 @@ + + + + + + diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py index 09a1dae9..67c7294c 100644 --- a/addons/account_voucher/models/account_voucher.py +++ b/addons/account_voucher/models/account_voucher.py @@ -4,7 +4,7 @@ from flectra import fields, models, api, _ from flectra.addons import decimal_precision as dp -from flectra.exceptions import UserError +from flectra.exceptions import UserError, ValidationError class AccountVoucher(models.Model): @@ -46,7 +46,7 @@ class AccountVoucher(models.Model): narration = fields.Text('Notes', readonly=True, states={'draft': [('readonly', False)]}) currency_id = fields.Many2one('res.currency', compute='_get_journal_currency', string='Currency', readonly=True, required=True, default=lambda self: self._get_currency()) - company_id = fields.Many2one('res.company', 'Company', + company_id = fields.Many2one('res.company', 'Company', store=True, required=True, readonly=True, states={'draft': [('readonly', False)]}, related='journal_id.company_id', default=lambda self: self._get_company()) state = fields.Selection([ @@ -74,6 +74,20 @@ class AccountVoucher(models.Model): ('pay_later', 'Pay Later'), ], 'Payment', index=True, readonly=True, states={'draft': [('readonly', False)]}, default='pay_later') date_due = fields.Date('Due Date', readonly=True, index=True, states={'draft': [('readonly', False)]}) + branch_id = fields.Many2one('res.branch', 'Branch', ondelete="restrict", + default=lambda self: self.env['res.users']._get_default_branch()) + + @api.constrains('company_id', 'branch_id') + def _check_company_branch(self): + for record in self: + if record.branch_id and record.company_id != record.branch_id.company_id: + raise ValidationError( + _('Configuration Error of Company:\n' + 'The Company (%s) in the voucher and ' + 'the Company (%s) of Branch must ' + 'be the same company!') % (record.company_id.name, + record.branch_id.company_id.name) + ) @api.one @api.depends('move_id.line_ids.reconciled', 'move_id.line_ids.account_id.internal_type') @@ -186,6 +200,7 @@ class AccountVoucher(models.Model): 'date': self.account_date, 'date_maturity': self.date_due, 'payment_id': self._context.get('payment_id'), + 'branch_id': self.branch_id.id, } return move_line @@ -199,13 +214,13 @@ class AccountVoucher(models.Model): name = self.journal_id.sequence_id.with_context(ir_sequence_date=self.date).next_by_id() else: raise UserError(_('Please define a sequence on the journal.')) - move = { 'name': name, 'journal_id': self.journal_id.id, 'narration': self.narration, 'date': self.account_date, 'ref': self.reference, + 'branch_id': self.branch_id.id } return move @@ -361,6 +376,10 @@ class AccountVoucherLine(models.Model): company_id = fields.Many2one('res.company', related='voucher_id.company_id', string='Company', store=True, readonly=True) tax_ids = fields.Many2many('account.tax', string='Tax', help="Only for tax excluded from price") currency_id = fields.Many2one('res.currency', related='voucher_id.currency_id') + branch_id = fields.Many2one('res.branch', + related='voucher_id.branch_id', + string='Branch', + readonly=True, store=True) @api.one @api.depends('price_unit', 'tax_ids', 'quantity', 'product_id', 'voucher_id.currency_id') diff --git a/addons/account_voucher/security/account_voucher_security.xml b/addons/account_voucher/security/account_voucher_security.xml index 5c9863de..f8818090 100644 --- a/addons/account_voucher/security/account_voucher_security.xml +++ b/addons/account_voucher/security/account_voucher_security.xml @@ -13,5 +13,20 @@ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + Vouchers Branch + + + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + Voucher lines branch + + diff --git a/addons/account_voucher/tests/__init__.py b/addons/account_voucher/tests/__init__.py new file mode 100644 index 00000000..2f9e20f7 --- /dev/null +++ b/addons/account_voucher/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import test_account_voucher_branch +from . import test_voucher_receipt_branch diff --git a/addons/account_voucher/tests/test_account_voucher_branch.py b/addons/account_voucher/tests/test_account_voucher_branch.py new file mode 100644 index 00000000..6f9b4ed0 --- /dev/null +++ b/addons/account_voucher/tests/test_account_voucher_branch.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +from datetime import date +from flectra.tests import common +from flectra.api import Environment + + + +class TestAccountVoucherBranch(common.TransactionCase): + + def setUp(self): + super(TestAccountVoucherBranch, self).setUp() + self.account_user = self.env.ref('account.group_account_manager') + self.model_account = self.env['account.account'] + self.main_company = self.env.ref('base.main_company') + self.model_journal = self.env['account.journal'] + self.partner = self.env.ref('base.res_partner_1') + self.model_voucher = self.env['account.voucher'] + self.apple_product = self.env.ref('product.product_product_7') + self.model_voucher_line = self.env['account.voucher.line'] + self.branch_1 = self.env.ref('base_branch_company.data_branch_1') + self.model_users = self.env['res.users'] + self.branch_2 = self.env.ref('base_branch_company.data_branch_2') + self.income_type = self.env['account.account.type'].search([('name', '=', 'Income')]) + account_obj = self.env.ref['account.account'] + self.account_receivable = account_obj.create( + {'code': 'X1012', 'name': 'Account Receivable - Test', + 'user_type_id': self.env.ref('account.data_account_type_receivable').id, + 'reconcile': True}) + self.account_1 = self.account_create('acc_code_1', self.income_type.id, self.main_company.id) + self.account_2 = self.account_create('acc_code_2', self.income_type.id, self.main_company.id) + self.journal_1 = self.journal_create('journal_code_1', self.account_1, self.main_company.id) + self.journal_2 = self.journal_create('journal_code_2', self.account_2, self.main_company.id) + self.branch_user_1 = self.user_create( + 'branch_user_1', self.branch_1, self.main_company, + [self.branch_1, self.branch_2], [self.account_user]) + self.branch_user_2 = self.user_create('branch_user_2', self.branch_2, self.main_company, [self.branch_2], + [self.account_user]) + + self.account_voucher_1 = self.receipt_create(self.journal_1, self.branch_1) + self.account_voucher_2 = self.receipt_create(self.journal_2, self.branch_2) + + def account_create(self, code, type, company_id): + data = {'code': code, + 'name': 'Test Sales Account ' + code, + 'company_id': company_id, + 'user_type_id': type, + } + account_obj = self.model_account.create(data) + return account_obj.id + + def journal_create(self, code, account_id, company_id): + data ={ + 'code': code, + 'name': 'Test Sales Account ' + code, + 'type': 'sale', + 'default_debit_account_id': account_id, + 'default_credit_account_id': account_id, + 'company_id': company_id + } + journal_obj = self.model_journal.create(data) + return journal_obj.id + + def user_create(self, user_name, branch_id, company_id, branch_ids, groups_ids ): + group_ids = [group.id for group in groups_ids] + data = { + 'login': user_name, + 'name': 'Test User ' + user_name, + 'email': 'demo@yourcompany.com', + 'branch_id':branch_id.id, + 'password': 'test@123', + 'company_id': company_id.id, + 'groups_id': [(6, 0, group_ids)], + 'company_ids': [(4, company_id.id)], + 'branch_ids': [(4, branch.id) for branch in branch_ids], + } + user_obj = self.model_users.with_context({'no_reset_password': True}).create(data) + return user_obj.id + + def receipt_create(self, journal_id, branch_id): + print ("@@@@@@@@@@@@@@@@",self.receivable_account ) + vals = { + 'name': 'Test Voucher', + 'partner_id': self.partner.id, + 'journal_id': journal_id, + 'voucher_type': 'sale', + 'account_id': self.receivable_account.id, + 'branch_id': branch_id.id, + 'company_id': self.main_company.id, + 'date': date.today(), + } + voucher_obj = self.model_voucher.create(vals) + line_vals = { + 'name': self.apple_product.name, + 'product_id': self.apple_product.id, + 'price_unit': 500, + 'quantity': 10, + 'account_id': self.receivable_account.id, + 'voucher_id': voucher_obj.id, + } + self.model_voucher_line.create(line_vals) + return voucher_obj + diff --git a/addons/account_voucher/tests/test_voucher_receipt_branch.py b/addons/account_voucher/tests/test_voucher_receipt_branch.py new file mode 100644 index 00000000..18ed6de0 --- /dev/null +++ b/addons/account_voucher/tests/test_voucher_receipt_branch.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +from . import test_account_voucher_branch as test_branch + + +class TestAccountVoucherReceiptBranch(test_branch.TestAccountVoucherBranch): + + def test_account_receipt_voucher(self): + + self.account_voucher_1.proforma_voucher() + self.account_voucher_2.proforma_voucher() + + branch_1_ids = all(line_id_1.branch_id.id == self.account_voucher_1.branch_id.id + for line_id_1 in self.account_voucher_1.move_id.line_ids) + branch_2_ids = all(line_id_2.branch_id.id == self.account_voucher_2.branch_id.id + for line_id_2 in self.account_voucher_2.move_id.line_ids) + self.assertNotEqual(branch_1_ids, False, 'Journal Entries of receipt has different branch id.') + self.assertNotEqual(branch_2_ids, False, 'Journal Entries of receipt has different branch id.') + + self.account_move = self.env['account.move'] + account_move_id_1 = self.account_voucher_1.move_id + account_move_id_2 = self.account_voucher_2.move_id + account_move_ids = self.account_move.sudo(self.branch_user_1).search( + [('id', 'in', [account_move_id_1.id, account_move_id_2.id])]) + self.assertEqual(len(account_move_ids), 2) + + account_move_ids = self.account_move.sudo(self.branch_user_2).search( + [('id', 'in', [account_move_id_1.id, account_move_id_2.id]), + ('branch_id', '=', self.branch_2.id)]) + self.assertEqual(len(account_move_ids), 1) diff --git a/addons/account_voucher/views/account_voucher_views.xml b/addons/account_voucher/views/account_voucher_views.xml index 4e0170cb..ea2f3371 100644 --- a/addons/account_voucher/views/account_voucher_views.xml +++ b/addons/account_voucher/views/account_voucher_views.xml @@ -233,6 +233,7 @@ + @@ -346,6 +347,7 @@ + diff --git a/addons/analytic/models/analytic_account.py b/addons/analytic/models/analytic_account.py index 531d1cb6..d3c65f4e 100644 --- a/addons/analytic/models/analytic_account.py +++ b/addons/analytic/models/analytic_account.py @@ -14,7 +14,7 @@ class AccountAnalyticTag(models.Model): class AccountAnalyticAccount(models.Model): _name = 'account.analytic.account' - _inherit = ['mail.thread'] + _inherit = ['mail.thread', 'ir.branch.company.mixin'] _description = 'Analytic Account' _order = 'code, name asc' @@ -105,4 +105,6 @@ class AccountAnalyticLine(models.Model): tag_ids = fields.Many2many('account.analytic.tag', 'account_analytic_line_tag_rel', 'line_id', 'tag_id', string='Tags', copy=True) company_id = fields.Many2one(related='account_id.company_id', string='Company', store=True, readonly=True) + branch_id = fields.Many2one(related='account_id.branch_id', + string='Branch', store=True, readonly=True) currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True) diff --git a/addons/analytic/security/analytic_security.xml b/addons/analytic/security/analytic_security.xml index bc6f92f2..95538733 100644 --- a/addons/analytic/security/analytic_security.xml +++ b/addons/analytic/security/analytic_security.xml @@ -15,6 +15,14 @@ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + Analytic Multi Branch + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + diff --git a/addons/analytic/views/analytic_account_views.xml b/addons/analytic/views/analytic_account_views.xml index a930635a..6a2763fa 100644 --- a/addons/analytic/views/analytic_account_views.xml +++ b/addons/analytic/views/analytic_account_views.xml @@ -51,6 +51,7 @@ + @@ -112,6 +113,7 @@ + @@ -136,6 +138,7 @@ + @@ -229,6 +232,7 @@ + diff --git a/addons/base_branch_company/demo/branch_demo.xml b/addons/base_branch_company/demo/branch_demo.xml index e71453f1..e9d580fc 100644 --- a/addons/base_branch_company/demo/branch_demo.xml +++ b/addons/base_branch_company/demo/branch_demo.xml @@ -38,5 +38,10 @@ + + + + + diff --git a/addons/sale/data/sale_demo.xml b/addons/sale/data/sale_demo.xml index 95120bda..e87de53e 100644 --- a/addons/sale/data/sale_demo.xml +++ b/addons/sale/data/sale_demo.xml @@ -328,6 +328,7 @@ + @@ -338,6 +339,7 @@ Laptop E5023 + 3 2450.00 @@ -345,6 +347,7 @@ + Mouse, Wireless 3 @@ -354,6 +357,7 @@ + @@ -364,6 +368,7 @@ + Laptop E5023 1 @@ -373,6 +378,7 @@ + Mouse, Wireless 2 @@ -381,6 +387,7 @@ + @@ -392,6 +399,7 @@ + Laptop E5023 1 @@ -401,6 +409,7 @@ + Mouse, Wireless 1 @@ -411,6 +420,7 @@ + @@ -420,6 +430,7 @@ + Laptop E5023 4 @@ -429,6 +440,7 @@ + Mouse, Wireless 4 @@ -438,6 +450,7 @@ + @@ -448,6 +461,7 @@ + Laptop E5023 4 @@ -457,6 +471,7 @@ + Mouse, Wireless 3 @@ -466,6 +481,7 @@ + @@ -476,6 +492,7 @@ + Laptop E5023 3 @@ -485,6 +502,7 @@ + Mouse, Wireless 3 @@ -494,6 +512,7 @@ + @@ -504,6 +523,7 @@ + Laptop E5023 2 @@ -513,6 +533,7 @@ + Mouse, Wireless 2 @@ -522,6 +543,7 @@ + @@ -532,6 +554,7 @@ + Laptop E5023 2 @@ -541,6 +564,7 @@ + Mouse, Wireless 2 diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index abb7a771..49d94d2e 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -13,13 +13,14 @@ from flectra.osv import expression from flectra.tools import float_is_zero, float_compare, DEFAULT_SERVER_DATETIME_FORMAT from flectra.tools.misc import formatLang - +from flectra.exceptions import ValidationError from flectra.addons import decimal_precision as dp class SaleOrder(models.Model): _name = "sale.order" - _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin', + 'ir.branch.company.mixin'] _description = "Quotation" _order = 'date_order desc, id desc' @@ -164,6 +165,17 @@ class SaleOrder(models.Model): product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product') + @api.multi + @api.constrains('branch_id', 'company_id') + def _check_company_branch(self): + for order in self: + if order.branch_id and order.company_id != order.branch_id.company_id: + raise ValidationError(_( + 'Configuration Error of Company:\n' + 'The Sales Order Company (%s) and the Company (%s) of ' + 'Branch must be the same!') % ( + order.company_id.name, order.branch_id.company_id.name)) + def _compute_portal_url(self): super(SaleOrder, self)._compute_portal_url() for order in self: @@ -354,6 +366,7 @@ class SaleOrder(models.Model): invoice_vals = { 'name': self.client_order_ref or '', 'origin': self.name, + 'branch_id': self.branch_id and self.branch_id.id, 'type': 'out_invoice', 'account_id': self.partner_invoice_id.property_account_receivable_id.id, 'partner_id': self.partner_invoice_id.id, @@ -548,6 +561,7 @@ class SaleOrder(models.Model): analytic = self.env['account.analytic.account'].create({ 'name': name, 'code': order.client_order_ref, + 'branch_id': order.branch_id and order.branch_id.id, 'company_id': order.company_id.id, 'partner_id': order.partner_id.id }) @@ -907,6 +921,8 @@ class SaleOrderLine(models.Model): salesman_id = fields.Many2one(related='order_id.user_id', store=True, string='Salesperson', readonly=True) currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True) company_id = fields.Many2one(related='order_id.company_id', string='Company', store=True, readonly=True) + branch_id = fields.Many2one(related='order_id.branch_id', + string='Branch', store=True, readonly=True) order_partner_id = fields.Many2one(related='order_id.partner_id', store=True, string='Customer') analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') is_downpayment = fields.Boolean( @@ -953,6 +969,7 @@ class SaleOrderLine(models.Model): 'name': self.name, 'sequence': self.sequence, 'origin': self.order_id.name, + 'branch_id': self.branch_id and self.branch_id.id, 'account_id': account.id, 'price_unit': self.price_unit, 'quantity': qty, diff --git a/addons/sale/report/report_all_channels_sales.py b/addons/sale/report/report_all_channels_sales.py index 2ae32b0e..aff06e38 100644 --- a/addons/sale/report/report_all_channels_sales.py +++ b/addons/sale/report/report_all_channels_sales.py @@ -6,6 +6,7 @@ from flectra import api, fields, models, tools class PosSaleReport(models.Model): _name = "report.all.channels.sales" + _inherit = ['ir.branch.company.mixin'] _description = "All sales orders grouped by sales channels" _auto = False @@ -37,6 +38,7 @@ class PosSaleReport(models.Model): so.user_id AS user_id, pt.categ_id AS categ_id, so.company_id AS company_id, + so.branch_id AS branch_id, sol.price_total / COALESCE(cr.rate, 1.0) AS price_total, so.pricelist_id AS pricelist_id, rp.country_id AS country_id, @@ -75,6 +77,7 @@ class PosSaleReport(models.Model): user_id, categ_id, company_id, + branch_id, price_total, pricelist_id, analytic_account_id, diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index 40508686..e45dcfa0 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -7,6 +7,7 @@ from flectra import api, fields, models class SaleReport(models.Model): _name = "sale.report" + _inherit = ['ir.branch.company.mixin'] _description = "Sales Orders Statistics" _auto = False _rec_name = 'date' @@ -68,6 +69,7 @@ class SaleReport(models.Model): s.partner_id as partner_id, s.user_id as user_id, s.company_id as company_id, + s.branch_id as branch_id, extract(epoch from avg(date_trunc('day',s.date_order)-date_trunc('day',s.create_date)))/(24*60*60)::decimal(16,2) as delay, t.categ_id as categ_id, s.pricelist_id as pricelist_id, @@ -111,6 +113,7 @@ class SaleReport(models.Model): s.user_id, s.state, s.company_id, + s.branch_id, s.pricelist_id, s.analytic_account_id, s.team_id, diff --git a/addons/sale/report/sale_report_templates.xml b/addons/sale/report/sale_report_templates.xml index 7818fef7..566fff4f 100644 --- a/addons/sale/report/sale_report_templates.xml +++ b/addons/sale/report/sale_report_templates.xml @@ -60,6 +60,10 @@ Payment Terms:

+

+ Branch: +

+

diff --git a/addons/sale/security/sale_security.xml b/addons/sale/security/sale_security.xml index 69ac5c76..92de2e97 100644 --- a/addons/sale/security/sale_security.xml +++ b/addons/sale/security/sale_security.xml @@ -84,6 +84,30 @@ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+ + + + + Sales Order multi-branch + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Sales Order Line multi-branch + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + + + Sales Order Analysis multi-branch + + + ['|',('branch_id','=', False),'|',('branch_id','=',user.default_branch_id.id), ('branch_id','in', [b.id for b in user.branch_ids])] + + Portal Personal Quotations/Sales Orders diff --git a/addons/sale/tests/__init__.py b/addons/sale/tests/__init__.py index c60d2736..81513778 100644 --- a/addons/sale/tests/__init__.py +++ b/addons/sale/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_sale_to_invoice from . import test_sale_order from . import test_product_id_change from . import test_sale_to_invoice_and_to_be_invoiced +from . import test_sale_branch diff --git a/addons/sale/tests/test_sale_branch.py b/addons/sale/tests/test_sale_branch.py new file mode 100644 index 00000000..1e9dba62 --- /dev/null +++ b/addons/sale/tests/test_sale_branch.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +from flectra.tests import common + + +class TestSaleBranch(common.TransactionCase): + def setUp(self): + super(TestSaleBranch, self).setUp() + + self.sale_obj = self.env['sale.order'] + + self.main_company = self.env.ref('base.main_company') + + self.payment_model_obj = self.env['sale.advance.payment.inv'] + + self.sale_user_group = self.env.ref('sales_team.group_sale_manager') + self.account_user_group = self.env.ref('account.group_account_invoice') + self.branch_1 = self.env.ref('base_branch_company.data_branch_1') + self.branch_2 = self.env.ref('base_branch_company.data_branch_2') + self.branch_3 = self.env.ref('base_branch_company.data_branch_3') + + self.sale_customer = self.env.ref('base.res_partner_2') + self.sale_pricelist = self.env.ref('product.list0') + + self.apple_product = self.env.ref('product.product_product_7') + self.apple_product.write({'invoice_policy': 'order'}) + + self.user_1 = self.create_sale_user( + self.main_company, 'user_1', self.branch_1, + [self.branch_1, self.branch_3], + [self.sale_user_group, self.account_user_group]) + self.user_2 = self.create_sale_user( + self.main_company, 'user_2', self.branch_3, + [self.branch_3], [self.sale_user_group, self.account_user_group]) + + self.so_1 = self.create_so( + self.sale_customer, self.apple_product, self.user_1.id, + self.branch_1, self.sale_pricelist) + self.so_2 = self.create_so( + self.sale_customer, self.apple_product, self.user_2.id, + self.branch_3, self.sale_pricelist) + + def create_sale_user(self, main_company, user_name, + branch_id, branch_ids, groups): + group_ids = [grp.id for grp in groups] + data = { + 'company_ids': [(4, main_company.id)], + 'branch_ids': [(4, ou.id) for ou in branch_ids], + 'company_id': main_company.id, + 'groups_id': [(6, 0, group_ids)], + 'default_branch_id': branch_id.id, + 'login': user_name, + 'name': 'Ron Sales User', + 'password': '123', + 'email': 'ron@yourcompany.com', + + } + user_obj = self.env['res.users'].create(data) + return user_obj + + def create_so(self, customer_id, product_id, + user_id, branch_id, pricelist_id): + data = { + 'partner_id': customer_id.id, + 'branch_id': branch_id.id, + 'pricelist_id': pricelist_id.id, + 'partner_shipping_id': customer_id.id, + 'partner_invoice_id': customer_id.id, + } + sale_id = self.sale_obj.sudo(user_id).create(data) + self.env['sale.order.line'].sudo(user_id).create({ + 'order_id': sale_id.id, + 'product_id': product_id.id, + 'name': 'Order Line' + }) + return sale_id + + def sale_order_confirm(self, sale_obj): + context = { + 'open_invoices': True, + 'active_id': sale_obj.id, + 'active_model': 'sale.order', + 'active_ids': sale_obj.ids, + + } + sale_obj.action_confirm() + + invoice = self.payment_model_obj.create({ + 'advance_payment_method': 'all', + }) + + result = invoice.with_context(context).create_invoices() + invoice = result['res_id'] + return invoice + + def get_sale_order(self, sale_order_id, branch_id): + sale = self.sale_obj.sudo(self.user_2.id).search( + [('id', '=', sale_order_id), + ('branch_id', '=', branch_id)]) + return sale + + def test_user_authentication(self): + sale = self.get_sale_order(self.so_1.id, self.branch_1.id) + self.assertEqual(sale.ids, [], 'Test User 2 should not have access to ' + 'Branch %s' % self.branch_1.name) + self.sale_order_confirm(self.so_1) + branch_3_invoice_id = self.sale_order_confirm(self.so_2) + branch_3 = self.env['account.invoice'].sudo(self.user_2.id).search( + [('id', '=', branch_3_invoice_id), + ('branch_id', '=', self.branch_3.id)]) + self.assertNotEqual(branch_3.ids, [], + 'Invoice should have branch_3 Branch') + + def test_user_authentication_2(self): + sale = self.get_sale_order(self.so_1.id, self.branch_1.id) + self.assertEqual(sale.ids, [], 'Test User 2 should ' + 'not have access to Branch %s' + % self.branch_1.name) + sale = self.get_sale_order(self.so_2.id, self.branch_3.id) + self.assertEqual(len(sale.ids), 1, 'Test User 1 should' + ' have access to Branch : %s' + % self.branch_3.name) diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 3ab72bf3..620e5aca 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -381,6 +381,7 @@ +
diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index c44ee16a..0db4c6c9 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -84,7 +84,7 @@ class CrmTeam(models.Model): @api.constrains('company_id', 'branch_id') def _check_company_branch(self): for record in self: - if record.company_id and record.company_id != record.branch_id.company_id: + if record.branch_id and record.company_id and record.company_id != record.branch_id.company_id: raise ValidationError( _('Configuration Error of Company:\n' 'The Company (%s) in the Team and '