class SaleOrder(models.Model): _inherit = "sale.order" discount_total = fields.Monetary( compute="_compute_discount_total", string="Discount Subtotal", currency_field="currency_id", store=True, ) price_total_no_discount = fields.Monetary( compute="_compute_discount_total", string="Subtotal Without Discount", currency_field="currency_id", store=True, ) @api.depends("order_line.discount_total", "order_line.price_total_no_discount") def _compute_discount_total(self): for order in self: discount_total = sum(order.order_line.mapped("discount_total")) price_total_no_discount = sum( order.order_line.mapped("price_total_no_discount")) order.update({ "discount_total": discount_total, "price_total_no_discount": price_total_no_discount, })
class MonetaryCustom(models.Model): _name = 'test_new_api.monetary_custom' _description = 'Monetary Related Custom' monetary_id = fields.Many2one('test_new_api.monetary_base') x_currency_id = fields.Many2one('res.currency', related='monetary_id.base_currency_id') x_amount = fields.Monetary(related='monetary_id.amount')
class Digest(models.Model): _inherit = 'digest.digest' kpi_account_total_revenue = fields.Boolean('Revenue') kpi_account_total_revenue_value = fields.Monetary( compute='_compute_kpi_account_total_revenue_value') def _compute_kpi_account_total_revenue_value(self): if not self.env.user.has_group('account.group_account_invoice'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() self._cr.execute( ''' SELECT SUM(line.debit) FROM account_move_line line JOIN account_move move ON move.id = line.move_id JOIN account_journal journal ON journal.id = move.journal_id WHERE line.company_id = %s AND line.date >= %s AND line.date < %s AND journal.type = 'sale' ''', [company.id, start, end]) query_res = self._cr.fetchone() record.kpi_account_total_revenue_value = query_res and query_res[ 0] or 0.0 def compute_kpis_actions(self, company, user): res = super(Digest, self).compute_kpis_actions(company, user) res['kpi_account_total_revenue'] = 'account.action_move_out_invoice_type&menu_id=%s' % self.env.ref( 'account.menu_finance').id return res
class MixedModel(models.Model): _name = 'test_new_api.mixed' _description = 'Test New API Mixed' number = fields.Float(digits=(10, 2), default=3.14) number2 = fields.Float(digits='New API Precision') date = fields.Date() moment = fields.Datetime() now = fields.Datetime(compute='_compute_now') lang = fields.Selection(string='Language', selection='_get_lang') reference = fields.Reference(string='Related Document', selection='_reference_models') comment1 = fields.Html(sanitize=False) comment2 = fields.Html(sanitize_attributes=True, strip_classes=False) comment3 = fields.Html(sanitize_attributes=True, strip_classes=True) comment4 = fields.Html(sanitize_attributes=True, strip_style=True) currency_id = fields.Many2one('res.currency', default=lambda self: self.env.ref('base.EUR')) amount = fields.Monetary() def _compute_now(self): # this is a non-stored computed field without dependencies for message in self: message.now = fields.Datetime.now() @api.model def _get_lang(self): return self.env['res.lang'].get_installed() @api.model def _reference_models(self): models = self.env['ir.model'].sudo().search([('state', '!=', 'manual')]) return [(model.model, model.name) for model in models if not model.model.startswith('ir.')]
class Digest(models.Model): _inherit = 'digest.digest' kpi_all_sale_total = fields.Boolean('All Sales') kpi_all_sale_total_value = fields.Monetary( compute='_compute_kpi_sale_total_value') def _compute_kpi_sale_total_value(self): if not self.env.user.has_group( 'sales_team.group_sale_salesman_all_leads'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() all_channels_sales = self.env['sale.report'].read_group( [('date', '>=', start), ('date', '<', end), ('state', 'not in', ['draft', 'cancel', 'sent']), ('company_id', '=', company.id)], ['price_total'], ['price_total']) record.kpi_all_sale_total_value = sum([ channel_sale['price_total'] for channel_sale in all_channels_sales ]) def compute_kpis_actions(self, company, user): res = super(Digest, self).compute_kpis_actions(company, user) res['kpi_all_sale_total'] = 'sale.report_all_channels_sales_action&menu_id=%s' % self.env.ref( 'sale.sale_menu_root').id return res
class FloatModel(models.Model): _name = model('float') _description = 'Tests: Base Import Model Float' value = fields.Float() value2 = fields.Monetary() currency_id = fields.Many2one('res.currency')
class Digest(models.Model): _inherit = 'digest.digest' kpi_website_sale_total = fields.Boolean('eCommerce Sales') kpi_website_sale_total_value = fields.Monetary( compute='_compute_kpi_website_sale_total_value') def _compute_kpi_website_sale_total_value(self): if not self.env.user.has_group( 'sales_team.group_sale_salesman_all_leads'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() confirmed_website_sales = self.env['sale.order'].search([ ('date_order', '>=', start), ('date_order', '<', end), ('state', 'not in', ['draft', 'cancel', 'sent']), ('website_id', '!=', False), ('company_id', '=', company.id) ]) record.kpi_website_sale_total_value = sum( confirmed_website_sales.mapped('amount_total')) def compute_kpis_actions(self, company, user): res = super(Digest, self).compute_kpis_actions(company, user) res['kpi_website_sale_total'] = 'website.backend_dashboard&menu_id=%s' % self.env.ref( 'website.menu_website_configuration').id return res
class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' lock_confirmed_po = fields.Boolean( "Lock Confirmed Orders", default=lambda self: self.env.company.po_lock == 'lock') po_lock = fields.Selection(related='company_id.po_lock', string="Purchase Order Modification *", readonly=False) po_order_approval = fields.Boolean("Purchase Order Approval", default=lambda self: self.env.company. po_double_validation == 'two_step') po_double_validation = fields.Selection( related='company_id.po_double_validation', string="Levels of Approvals *", readonly=False) po_double_validation_amount = fields.Monetary( related='company_id.po_double_validation_amount', string="Minimum Amount", currency_field='company_currency_id', readonly=False) company_currency_id = fields.Many2one( 'res.currency', related='company_id.currency_id', string="Company Currency", readonly=True, help='Utility field to express amount currency') default_purchase_method = fields.Selection( [ ('purchase', 'Ordered quantities'), ('receive', 'Received quantities'), ], string="Bill Control", default_model="product.template", help="This default value is applied to any new product created. " "This can be changed in the product detail form.", default="receive") group_warning_purchase = fields.Boolean( "Purchase Warnings", implied_group='purchase.group_warning_purchase') module_account_3way_match = fields.Boolean( "3-way matching: purchases, receptions and bills") module_purchase_requisition = fields.Boolean("Purchase Agreements") module_purchase_product_matrix = fields.Boolean("Purchase Grid Entry") po_lead = fields.Float(related='company_id.po_lead', readonly=False) use_po_lead = fields.Boolean( string="Security Lead Time for Purchase", config_parameter='purchase.use_po_lead', help= "Margin of error for vendor lead times. When the system generates Purchase Orders for reordering products,they will be scheduled that many days earlier to cope with unexpected vendor delays." ) @api.onchange('use_po_lead') def _onchange_use_po_lead(self): if not self.use_po_lead: self.po_lead = 0.0 def set_values(self): super(ResConfigSettings, self).set_values() self.po_lock = 'lock' if self.lock_confirmed_po else 'edit' self.po_double_validation = 'two_step' if self.po_order_approval else 'one_step'
class Digest(models.Model): _inherit = 'digest.digest' kpi_pos_total = fields.Boolean('POS Sales') kpi_pos_total_value = fields.Monetary( compute='_compute_kpi_pos_total_value') def _compute_kpi_pos_total_value(self): if not self.env.user.has_group('point_of_sale.group_pos_user'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() record.kpi_pos_total_value = sum(self.env['pos.order'].search([ ('date_order', '>=', start), ('date_order', '<', end), ('state', 'not in', ['draft', 'cancel', 'invoiced']), ('company_id', '=', company.id) ]).mapped('amount_total')) def compute_kpis_actions(self, company, user): res = super(Digest, self).compute_kpis_actions(company, user) res['kpi_pos_total'] = 'point_of_sale.action_pos_sale_graph&menu_id=%s' % self.env.ref( 'point_of_sale.menu_point_root').id return res
class HrEmployee(models.Model): _inherit = 'hr.employee' timesheet_cost = fields.Monetary('Timesheet Cost', currency_field='currency_id', groups="hr.group_hr_user", default=0.0) currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True)
class ComplexModel(models.Model): _name = model('complex') _description = 'Tests: Base Import Model Complex' f = fields.Float() m = fields.Monetary() c = fields.Char() currency_id = fields.Many2one('res.currency') d = fields.Date() dt = fields.Datetime()
class MonetaryOrder(models.Model): _name = 'test_new_api.monetary_order' _description = 'Sales Order' currency_id = fields.Many2one('res.currency') line_ids = fields.One2many('test_new_api.monetary_order_line', 'order_id') total = fields.Monetary(compute='_compute_total', store=True) @api.depends('line_ids.subtotal') def _compute_total(self): for record in self: record.total = sum(line.subtotal for line in record.line_ids)
class ResPartner(models.Model): """User inherits partner, so we are implicitly adding these fields to User This essentially reproduces the (sad) situation introduced by account. """ _name = 'res.partner' _inherit = 'res.partner' currency_id = fields.Many2one('res.currency', compute='_get_company_currency', readonly=True) monetary = fields.Monetary( ) # implicitly depends on currency_id as currency_field def _get_company_currency(self): for partner in self: partner.currency_id = partner.sudo().company_id.currency_id
class Company(models.Model): _inherit = 'res.company' po_lead = fields.Float(string='Purchase Lead Time', required=True, help="Margin of error for vendor lead times. When the system " "generates Purchase Orders for procuring products, " "they will be scheduled that many days earlier " "to cope with unexpected vendor delays.", default=0.0) po_lock = fields.Selection([ ('edit', 'Allow to edit purchase orders'), ('lock', 'Confirmed purchase orders are not editable') ], string="Purchase Order Modification", default="edit", help='Purchase Order Modification used when you want to purchase order editable after confirm') po_double_validation = fields.Selection([ ('one_step', 'Confirm purchase orders in one step'), ('two_step', 'Get 2 levels of approvals to confirm a purchase order') ], string="Levels of Approvals", default='one_step', help="Provide a double validation mechanism for purchases") po_double_validation_amount = fields.Monetary(string='Double validation amount', default=5000, help="Minimum amount for which a double validation is required")
class SaleOrder(models.Model): _inherit = "sale.order" margin = fields.Monetary(compute='_product_margin', help="It gives profitability by calculating the difference between the Unit Price and the cost.", currency_field='currency_id', store=True) @api.depends('order_line.margin') def _product_margin(self): if not all(self._ids): for order in self: order.margin = sum(order.order_line.filtered(lambda r: r.state != 'cancel').mapped('margin')) else: self.env["sale.order.line"].flush(['margin', 'state']) # On batch records recomputation (e.g. at install), compute the margins # with a single read_group query for better performance. # This isn't done in an onchange environment because (part of) the data # may not be stored in database (new records or unsaved modifications). grouped_order_lines_data = self.env['sale.order.line'].read_group( [ ('order_id', 'in', self.ids), ('state', '!=', 'cancel'), ], ['margin', 'order_id'], ['order_id']) mapped_data = {m['order_id'][0]: m['margin'] for m in grouped_order_lines_data} for order in self: order.margin = mapped_data.get(order.id, 0.0)
class AccountAnalyticAccount(models.Model): _name = 'account.analytic.account' _inherit = ['mail.thread'] _description = 'Analytic Account' _order = 'code, name asc' @api.model def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): """ Override read_group to calculate the sum of the non-stored fields that depend on the user context """ res = super(AccountAnalyticAccount, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy) accounts = self.env['account.analytic.account'] for line in res: if '__domain' in line: accounts = self.search(line['__domain']) if 'balance' in fields: line['balance'] = sum(accounts.mapped('balance')) if 'debit' in fields: line['debit'] = sum(accounts.mapped('debit')) if 'credit' in fields: line['credit'] = sum(accounts.mapped('credit')) return res @api.depends('line_ids.amount') def _compute_debit_credit_balance(self): Curr = self.env['res.currency'] analytic_line_obj = self.env['account.analytic.line'] domain = [('account_id', 'in', self.ids)] if self._context.get('from_date', False): domain.append(('date', '>=', self._context['from_date'])) if self._context.get('to_date', False): domain.append(('date', '<=', self._context['to_date'])) if self._context.get('tag_ids'): tag_domain = expression.OR([[('tag_ids', 'in', [tag])] for tag in self._context['tag_ids']]) domain = expression.AND([domain, tag_domain]) if self._context.get('company_ids'): domain.append(('company_id', 'in', self._context['company_ids'])) user_currency = self.env.company.currency_id credit_groups = analytic_line_obj.read_group( domain=domain + [('amount', '>=', 0.0)], fields=['account_id', 'currency_id', 'amount'], groupby=['account_id', 'currency_id'], lazy=False, ) data_credit = defaultdict(float) for l in credit_groups: data_credit[l['account_id'][0]] += Curr.browse( l['currency_id'][0])._convert(l['amount'], user_currency, self.env.company, fields.Date.today()) debit_groups = analytic_line_obj.read_group( domain=domain + [('amount', '<', 0.0)], fields=['account_id', 'currency_id', 'amount'], groupby=['account_id', 'currency_id'], lazy=False, ) data_debit = defaultdict(float) for l in debit_groups: data_debit[l['account_id'][0]] += Curr.browse( l['currency_id'][0])._convert(l['amount'], user_currency, self.env.company, fields.Date.today()) for account in self: account.debit = abs(data_debit.get(account.id, 0.0)) account.credit = data_credit.get(account.id, 0.0) account.balance = account.credit - account.debit name = fields.Char(string='Analytic Account', index=True, required=True, tracking=True) code = fields.Char(string='Reference', index=True, tracking=True) active = fields.Boolean( 'Active', help= "If the active field is set to False, it will allow you to hide the account without removing it.", default=True) group_id = fields.Many2one( 'account.analytic.group', string='Group', domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") line_ids = fields.One2many('account.analytic.line', 'account_id', string="Analytic Lines") company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company) # use auto_join to speed up name_search call partner_id = fields.Many2one( 'res.partner', string='Customer', auto_join=True, tracking=True, domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") balance = fields.Monetary(compute='_compute_debit_credit_balance', string='Balance') debit = fields.Monetary(compute='_compute_debit_credit_balance', string='Debit') credit = fields.Monetary(compute='_compute_debit_credit_balance', string='Credit') currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True) def name_get(self): res = [] for analytic in self: name = analytic.name if analytic.code: name = '[' + analytic.code + '] ' + name if analytic.partner_id.commercial_partner_id.name: name = name + ' - ' + analytic.partner_id.commercial_partner_id.name res.append((analytic.id, name)) return res @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): if operator not in ('ilike', 'like', '=', '=like', '=ilike'): return super(AccountAnalyticAccount, self)._name_search(name, args, operator, limit, name_get_uid=name_get_uid) args = args or [] if operator == 'ilike' and not (name or '').strip(): domain = [] else: # `partner_id` is in auto_join and the searches using ORs with auto_join fields doesn't work # we have to cut the search in two searches ... https://github.com/harpiya/harpiya/issues/25175 partner_ids = self.env['res.partner']._search( [('name', operator, name)], limit=limit, access_rights_uid=name_get_uid) domain = [ '|', '|', ('code', operator, name), ('name', operator, name), ('partner_id', 'in', partner_ids) ] analytic_account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) return models.lazy_name_get( self.browse(analytic_account_ids).with_user(name_get_uid))
class AccountMoveReversal(models.TransientModel): """ Account move reversal wizard, it cancel an account move by reversing it. """ _name = 'account.move.reversal' _description = 'Account Move Reversal' move_id = fields.Many2one('account.move', string='Journal Entry', domain=[('state', '=', 'posted'), ('type', 'not in', ('out_refund', 'in_refund'))]) date = fields.Date(string='Reversal date', default=fields.Date.context_today, required=True) reason = fields.Char(string='Reason') refund_method = fields.Selection( selection=[('refund', 'Partial Refund'), ('cancel', 'Full Refund'), ('modify', 'Full refund and new draft invoice')], string='Credit Method', required=True, help= 'Choose how you want to credit this invoice. You cannot "modify" nor "cancel" if the invoice is already reconciled.' ) journal_id = fields.Many2one( 'account.journal', string='Use Specific Journal', help='If empty, uses the journal of the journal entry to be reversed.') # computed fields residual = fields.Monetary(compute="_compute_from_moves") currency_id = fields.Many2one('res.currency', compute="_compute_from_moves") move_type = fields.Char(compute="_compute_from_moves") @api.model def default_get(self, fields): res = super(AccountMoveReversal, self).default_get(fields) move_ids = self.env['account.move'].browse( self.env.context['active_ids']) if self.env.context.get( 'active_model') == 'account.move' else self.env['account.move'] res['refund_method'] = (len(move_ids) > 1 or move_ids.type == 'entry') and 'cancel' or 'refund' res['residual'] = len(move_ids) == 1 and move_ids.amount_residual or 0 res['currency_id'] = len( move_ids.currency_id) == 1 and move_ids.currency_id.id or False res['move_type'] = len(move_ids) == 1 and move_ids.type or False res['move_id'] = move_ids[0].id if move_ids else False return res @api.depends('move_id') def _compute_from_moves(self): move_ids = self.env['account.move'].browse( self.env.context['active_ids']) if self.env.context.get( 'active_model') == 'account.move' else self.move_id for record in self: record.residual = len( move_ids) == 1 and move_ids.amount_residual or 0 record.currency_id = len( move_ids.currency_id) == 1 and move_ids.currency_id or False record.move_type = len(move_ids) == 1 and move_ids.type or False def _prepare_default_reversal(self, move): return { 'ref': _('Reversal of: %s, %s') % (move.name, self.reason) if self.reason else _('Reversal of: %s') % (move.name), 'date': self.date or move.date, 'invoice_date': move.is_invoice(include_receipts=True) and (self.date or move.date) or False, 'journal_id': self.journal_id and self.journal_id.id or move.journal_id.id, 'invoice_payment_term_id': None, 'auto_post': True if self.date > fields.Date.context_today(self) else False, 'invoice_user_id': move.invoice_user_id.id, } def reverse_moves(self): moves = self.env['account.move'].browse( self.env.context['active_ids']) if self.env.context.get( 'active_model') == 'account.move' else self.move_id # Create default values. default_values_list = [] for move in moves: default_values_list.append(self._prepare_default_reversal(move)) # Handle reverse method. if self.refund_method == 'cancel': if any( [vals.get('auto_post', False) for vals in default_values_list]): new_moves = moves._reverse_moves(default_values_list) else: new_moves = moves._reverse_moves(default_values_list, cancel=True) elif self.refund_method == 'modify': moves._reverse_moves(default_values_list, cancel=True) moves_vals_list = [] for move in moves.with_context(include_business_fields=True): moves_vals_list.append( move.copy_data({ 'date': self.date or move.date, })[0]) new_moves = self.env['account.move'].create(moves_vals_list) elif self.refund_method == 'refund': new_moves = moves._reverse_moves(default_values_list) else: return # Create action. action = { 'name': _('Reverse Moves'), 'type': 'ir.actions.act_window', 'res_model': 'account.move', } if len(new_moves) == 1: action.update({ 'view_mode': 'form', 'res_id': new_moves.id, }) else: action.update({ 'view_mode': 'tree,form', 'domain': [('id', 'in', new_moves.ids)], }) return action
class Contract(models.Model): _name = 'hr.contract' _description = 'Contract' _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char('Contract Reference', required=True) active = fields.Boolean(default=True) employee_id = fields.Many2one( 'hr.employee', string='Employee', tracking=True, domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") department_id = fields.Many2one( 'hr.department', domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]", string="Department") job_id = fields.Many2one( 'hr.job', domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]", string='Job Position') date_start = fields.Date('Start Date', required=True, default=fields.Date.today, tracking=True, help="Start date of the contract.") date_end = fields.Date( 'End Date', tracking=True, help="End date of the contract (if it's a fixed-term contract).") trial_date_end = fields.Date( 'End of Trial Period', help="End date of the trial period (if there is one).") resource_calendar_id = fields.Many2one( 'resource.calendar', 'Working Schedule', default=lambda self: self.env.company.resource_calendar_id.id, copy=False, domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") wage = fields.Monetary('Wage', required=True, tracking=True, help="Employee's monthly gross wage.") advantages = fields.Text('Advantages') notes = fields.Text('Notes') state = fields.Selection([('draft', 'New'), ('open', 'Running'), ('close', 'Expired'), ('cancel', 'Cancelled')], string='Status', group_expand='_expand_states', copy=False, tracking=True, help='Status of the contract', default='draft') company_id = fields.Many2one('res.company', default=lambda self: self.env.company, required=True) """ kanban_state: * draft + green = "Incoming" state (will be set as Open once the contract has started) * open + red = "Pending" state (will be set as Closed once the contract has ended) * red = Shows a warning on the employees kanban view """ kanban_state = fields.Selection([('normal', 'Grey'), ('done', 'Green'), ('blocked', 'Red')], string='Kanban State', default='normal', tracking=True, copy=False) currency_id = fields.Many2one(string="Currency", related='company_id.currency_id', readonly=True) permit_no = fields.Char('Work Permit No', related="employee_id.permit_no", readonly=False) visa_no = fields.Char('Visa No', related="employee_id.visa_no", readonly=False) visa_expire = fields.Date('Visa Expire Date', related="employee_id.visa_expire", readonly=False) hr_responsible_id = fields.Many2one( 'res.users', 'HR Responsible', tracking=True, help='Person responsible for validating the employee\'s contracts.') calendar_mismatch = fields.Boolean(compute='_compute_calendar_mismatch') @api.depends('employee_id.resource_calendar_id', 'resource_calendar_id') def _compute_calendar_mismatch(self): for contract in self: contract.calendar_mismatch = contract.resource_calendar_id != contract.employee_id.resource_calendar_id def _expand_states(self, states, domain, order): return [key for key, val in type(self).state.selection] @api.onchange('employee_id') def _onchange_employee_id(self): if self.employee_id: self.job_id = self.employee_id.job_id self.department_id = self.employee_id.department_id self.resource_calendar_id = self.employee_id.resource_calendar_id self.company_id = self.employee_id.company_id @api.constrains('employee_id', 'state', 'kanban_state', 'date_start', 'date_end') def _check_current_contract(self): """ Two contracts in state [incoming | open | close] cannot overlap """ for contract in self.filtered(lambda c: ( c.state not in ['draft', 'cancel'] or c.state == 'draft' and c. kanban_state == 'done') and c.employee_id): domain = [ ('id', '!=', contract.id), ('employee_id', '=', contract.employee_id.id), '|', ('state', 'in', ['open', 'close']), '&', ('state', '=', 'draft'), ('kanban_state', '=', 'done') # replaces incoming ] if not contract.date_end: start_domain = [] end_domain = [ '|', ('date_end', '>=', contract.date_start), ('date_end', '=', False) ] else: start_domain = [('date_start', '<=', contract.date_end)] end_domain = [ '|', ('date_end', '>', contract.date_start), ('date_end', '=', False) ] domain = expression.AND([domain, start_domain, end_domain]) if self.search_count(domain): raise ValidationError( _('An employee can only have one contract at the same time. (Excluding Draft and Cancelled contracts)' )) @api.constrains('date_start', 'date_end') def _check_dates(self): if self.filtered(lambda c: c.date_end and c.date_start > c.date_end): raise ValidationError( _('Contract start date must be earlier than contract end date.' )) @api.model def update_state(self): self.search([ ('state', '=', 'open'), '|', '&', ('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=7))), ('date_end', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))), '&', ('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=60))), ('visa_expire', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))), ]).write({'kanban_state': 'blocked'}) self.search([ ('state', '=', 'open'), '|', ('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))), ('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))), ]).write({'state': 'close'}) self.search([ ('state', '=', 'draft'), ('kanban_state', '=', 'done'), ('date_start', '<=', fields.Date.to_string(date.today())), ]).write({'state': 'open'}) contract_ids = self.search([('date_end', '=', False), ('state', '=', 'close'), ('employee_id', '!=', False)]) # Ensure all closed contract followed by a new contract have a end date. # If closed contract has no closed date, the work entries will be generated for an unlimited period. for contract in contract_ids: next_contract = self.search( [('employee_id', '=', contract.employee_id.id), ('state', 'not in', ['cancel', 'new']), ('date_start', '>', contract.date_start)], order="date_start asc", limit=1) if next_contract: contract.date_end = next_contract.date_start - relativedelta( days=1) continue next_contract = self.search( [('employee_id', '=', contract.employee_id.id), ('date_start', '>', contract.date_start)], order="date_start asc", limit=1) if next_contract: contract.date_end = next_contract.date_start - relativedelta( days=1) return True def _assign_open_contract(self): for contract in self: contract.employee_id.sudo().write({'contract_id': contract.id}) def write(self, vals): res = super(Contract, self).write(vals) if vals.get('state') == 'open': self._assign_open_contract() if vals.get('state') == 'close': for contract in self.filtered(lambda c: not c.date_end): contract.date_end = max(date.today(), contract.date_start) calendar = vals.get('resource_calendar_id') if calendar and (self.state == 'open' or (self.state == 'draft' and self.kanban_state == 'done')): self.mapped('employee_id').write( {'resource_calendar_id': calendar}) if 'state' in vals and 'kanban_state' not in vals: self.write({'kanban_state': 'normal'}) return res @api.model def create(self, vals): contracts = super(Contract, self).create(vals) if vals.get('state') == 'open': contracts._assign_open_contract() open_contracts = contracts.filtered( lambda c: c.state == 'open' or c.state == 'draft' and c. kanban_state == 'done') # sync contract calendar -> calendar employee for contract in open_contracts.filtered( lambda c: c.employee_id and c.resource_calendar_id): contract.employee_id.resource_calendar_id = contract.resource_calendar_id return contracts def _track_subtype(self, init_values): self.ensure_one() if 'state' in init_values and self.state == 'open' and 'kanban_state' in init_values and self.kanban_state == 'blocked': return self.env.ref('hr_contract.mt_contract_pending') elif 'state' in init_values and self.state == 'close': return self.env.ref('hr_contract.mt_contract_close') return super(Contract, self)._track_subtype(init_values)
class CrmLead(models.Model): _inherit = 'crm.lead' sale_amount_total = fields.Monetary( compute='_compute_sale_data', string="Sum of Orders", help="Untaxed Total of Confirmed Orders", currency_field='company_currency') quotation_count = fields.Integer(compute='_compute_sale_data', string="Number of Quotations") sale_order_count = fields.Integer(compute='_compute_sale_data', string="Number of Sale Orders") order_ids = fields.One2many('sale.order', 'opportunity_id', string='Orders') @api.depends('order_ids.state', 'order_ids.currency_id', 'order_ids.amount_untaxed', 'order_ids.date_order', 'order_ids.company_id') def _compute_sale_data(self): for lead in self: total = 0.0 quotation_cnt = 0 sale_order_cnt = 0 company_currency = lead.company_currency or self.env.company.currency_id for order in lead.order_ids: if order.state in ('draft', 'sent'): quotation_cnt += 1 if order.state not in ('draft', 'sent', 'cancel'): sale_order_cnt += 1 total += order.currency_id._convert( order.amount_untaxed, company_currency, order.company_id, order.date_order or fields.Date.today()) lead.sale_amount_total = total lead.quotation_count = quotation_cnt lead.sale_order_count = sale_order_cnt @api.model def retrieve_sales_dashboard(self): res = super(CrmLead, self).retrieve_sales_dashboard() date_today = fields.Date.from_string(fields.Date.context_today(self)) res['invoiced'] = { 'this_month': 0, 'last_month': 0, } account_invoice_domain = [ ('state', '=', 'posted'), ('invoice_user_id', '=', self.env.uid), ('invoice_date', '>=', date_today.replace(day=1) - relativedelta(months=+1)), ('type', 'in', ['out_invoice', 'out_refund']) ] invoice_data = self.env['account.move'].search_read( account_invoice_domain, ['invoice_date', 'amount_untaxed', 'type']) for invoice in invoice_data: if invoice['invoice_date']: invoice_date = fields.Date.from_string(invoice['invoice_date']) sign = 1 if invoice['type'] == 'out_invoice' else -1 if invoice_date <= date_today and invoice_date >= date_today.replace( day=1): res['invoiced'][ 'this_month'] += sign * invoice['amount_untaxed'] elif invoice_date < date_today.replace( day=1) and invoice_date >= date_today.replace( day=1) - relativedelta(months=+1): res['invoiced'][ 'last_month'] += sign * invoice['amount_untaxed'] res['invoiced']['target'] = self.env.user.target_sales_invoiced return res def action_sale_quotations_new(self): if not self.partner_id: return self.env.ref( "sale_crm.crm_quotation_partner_action").read()[0] else: return self.action_new_quotation() def action_new_quotation(self): action = self.env.ref("sale_crm.sale_action_quotations_new").read()[0] action['context'] = { 'search_default_opportunity_id': self.id, 'default_opportunity_id': self.id, 'search_default_partner_id': self.partner_id.id, 'default_partner_id': self.partner_id.id, 'default_team_id': self.team_id.id, 'default_campaign_id': self.campaign_id.id, 'default_medium_id': self.medium_id.id, 'default_origin': self.name, 'default_source_id': self.source_id.id, 'default_company_id': self.company_id.id or self.env.company.id, 'default_tag_ids': self.tag_ids.ids, } return action def action_view_sale_quotation(self): action = self.env.ref( 'sale.action_quotations_with_onboarding').read()[0] action['context'] = { 'search_default_draft': 1, 'search_default_partner_id': self.partner_id.id, 'default_partner_id': self.partner_id.id, 'default_opportunity_id': self.id } action['domain'] = [('opportunity_id', '=', self.id), ('state', 'in', ['draft', 'sent'])] quotations = self.mapped('order_ids').filtered(lambda l: l.state in ('draft', 'sent')) if len(quotations) == 1: action['views'] = [(self.env.ref('sale.view_order_form').id, 'form')] action['res_id'] = quotations.id return action def action_view_sale_order(self): action = self.env.ref('sale.action_orders').read()[0] action['context'] = { 'search_default_partner_id': self.partner_id.id, 'default_partner_id': self.partner_id.id, 'default_opportunity_id': self.id, } action['domain'] = [('opportunity_id', '=', self.id), ('state', 'not in', ('draft', 'sent', 'cancel'))] orders = self.mapped('order_ids').filtered(lambda l: l.state not in ('draft', 'sent', 'cancel')) if len(orders) == 1: action['views'] = [(self.env.ref('sale.view_order_form').id, 'form')] action['res_id'] = orders.id return action
class ResPartner(models.Model): _name = 'res.partner' _inherit = 'res.partner' @api.depends_context('force_company') def _credit_debit_get(self): tables, where_clause, where_params = self.env[ 'account.move.line'].with_context( state='posted', company_id=self.env.company.id)._query_get() where_params = [tuple(self.ids)] + where_params if where_clause: where_clause = 'AND ' + where_clause self._cr.execute( """SELECT account_move_line.partner_id, act.type, SUM(account_move_line.amount_residual) FROM """ + tables + """ LEFT JOIN account_account a ON (account_move_line.account_id=a.id) LEFT JOIN account_account_type act ON (a.user_type_id=act.id) WHERE act.type IN ('receivable','payable') AND account_move_line.partner_id IN %s AND account_move_line.reconciled IS NOT TRUE """ + where_clause + """ GROUP BY account_move_line.partner_id, act.type """, where_params) treated = self.browse() for pid, type, val in self._cr.fetchall(): partner = self.browse(pid) if type == 'receivable': partner.credit = val if partner not in treated: partner.debit = False treated |= partner elif type == 'payable': partner.debit = -val if partner not in treated: partner.credit = False treated |= partner remaining = (self - treated) remaining.debit = False remaining.credit = False def _asset_difference_search(self, account_type, operator, operand): if operator not in ('<', '=', '>', '>=', '<='): return [] if type(operand) not in (float, int): return [] sign = 1 if account_type == 'payable': sign = -1 res = self._cr.execute( ''' SELECT partner.id FROM res_partner partner LEFT JOIN account_move_line aml ON aml.partner_id = partner.id JOIN account_move move ON move.id = aml.move_id RIGHT JOIN account_account acc ON aml.account_id = acc.id WHERE acc.internal_type = %s AND NOT acc.deprecated AND acc.company_id = %s AND move.state = 'posted' GROUP BY partner.id HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, self.env.user.company_id.id, sign, operand)) res = self._cr.fetchall() if not res: return [('id', '=', '0')] return [('id', 'in', [r[0] for r in res])] @api.model def _credit_search(self, operator, operand): return self._asset_difference_search('receivable', operator, operand) @api.model def _debit_search(self, operator, operand): return self._asset_difference_search('payable', operator, operand) def _invoice_total(self): account_invoice_report = self.env['account.invoice.report'] if not self.ids: return True user_currency_id = self.env.company.currency_id.id all_partners_and_children = {} all_partner_ids = [] for partner in self: # price_total is in the company currency all_partners_and_children[partner] = self.with_context( active_test=False).search([('id', 'child_of', partner.id)]).ids all_partner_ids += all_partners_and_children[partner] # searching account.invoice.report via the ORM is comparatively expensive # (generates queries "id in []" forcing to build the full table). # In simple cases where all invoices are in the same currency than the user's company # access directly these elements # generate where clause to include multicompany rules where_query = account_invoice_report._where_calc([ ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('type', 'in', ('out_invoice', 'out_refund')) ]) account_invoice_report._apply_ir_rules(where_query, 'read') from_clause, where_clause, where_clause_params = where_query.get_sql() # price_total is in the company currency query = """ SELECT SUM(price_subtotal) as total, partner_id FROM account_invoice_report account_invoice_report WHERE %s GROUP BY partner_id """ % where_clause self.env.cr.execute(query, where_clause_params) price_totals = self.env.cr.dictfetchall() for partner, child_ids in all_partners_and_children.items(): partner.total_invoiced = sum(price['total'] for price in price_totals if price['partner_id'] in child_ids) def _compute_journal_item_count(self): AccountMoveLine = self.env['account.move.line'] for partner in self: partner.journal_item_count = AccountMoveLine.search_count([ ('partner_id', '=', partner.id) ]) def _compute_has_unreconciled_entries(self): for partner in self: # Avoid useless work if has_unreconciled_entries is not relevant for this partner if not partner.active or not partner.is_company and partner.parent_id: partner.has_unreconciled_entries = False continue self.env.cr.execute( """ SELECT 1 FROM( SELECT p.last_time_entries_checked AS last_time_entries_checked, MAX(l.write_date) AS max_date FROM account_move_line l RIGHT JOIN account_account a ON (a.id = l.account_id) RIGHT JOIN res_partner p ON (l.partner_id = p.id) WHERE p.id = %s AND EXISTS ( SELECT 1 FROM account_move_line l WHERE l.account_id = a.id AND l.partner_id = p.id AND l.amount_residual > 0 ) AND EXISTS ( SELECT 1 FROM account_move_line l WHERE l.account_id = a.id AND l.partner_id = p.id AND l.amount_residual < 0 ) GROUP BY p.last_time_entries_checked ) as s WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked) """, (partner.id, )) partner.has_unreconciled_entries = self.env.cr.rowcount == 1 def mark_as_reconciled(self): self.env['account.partial.reconcile'].check_access_rights('write') return self.sudo().with_context(company_id=self.env.company.id).write({ 'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) }) def _get_company_currency(self): for partner in self: if partner.company_id: partner.currency_id = partner.sudo().company_id.currency_id else: partner.currency_id = self.env.company.currency_id credit = fields.Monetary(compute='_credit_debit_get', search=_credit_search, string='Total Receivable', help="Total amount this customer owes you.") debit = fields.Monetary( compute='_credit_debit_get', search=_debit_search, string='Total Payable', help="Total amount you have to pay to this vendor.") debit_limit = fields.Monetary('Payable Limit') total_invoiced = fields.Monetary(compute='_invoice_total', string="Total Invoiced", groups='account.group_account_invoice') currency_id = fields.Many2one( 'res.currency', compute='_get_company_currency', readonly=True, string="Currency", help='Utility field to express amount currency') journal_item_count = fields.Integer(compute='_compute_journal_item_count', string="Journal Items", type="integer") property_account_payable_id = fields.Many2one( 'account.account', company_dependent=True, string="Account Payable", domain= "[('internal_type', '=', 'payable'), ('deprecated', '=', False)]", help= "This account will be used instead of the default one as the payable account for the current partner", required=True) property_account_receivable_id = fields.Many2one( 'account.account', company_dependent=True, string="Account Receivable", domain= "[('internal_type', '=', 'receivable'), ('deprecated', '=', False)]", help= "This account will be used instead of the default one as the receivable account for the current partner", required=True) property_account_position_id = fields.Many2one( 'account.fiscal.position', company_dependent=True, string="Fiscal Position", help= "The fiscal position determines the taxes/accounts used for this contact." ) property_payment_term_id = fields.Many2one( 'account.payment.term', company_dependent=True, string='Customer Payment Terms', help= "This payment term will be used instead of the default one for sales orders and customer invoices" ) property_supplier_payment_term_id = fields.Many2one( 'account.payment.term', company_dependent=True, string='Vendor Payment Terms', help= "This payment term will be used instead of the default one for purchase orders and vendor bills" ) ref_company_ids = fields.One2many( 'res.company', 'partner_id', string='Companies that refers to partner') has_unreconciled_entries = fields.Boolean( compute='_compute_has_unreconciled_entries', help= "The partner has at least one unreconciled debit and credit since last time the invoices & payments matching was performed." ) last_time_entries_checked = fields.Datetime( string='Latest Invoices & Payments Matching Date', readonly=True, copy=False, help= 'Last time the invoices & payments matching was performed for this partner. ' 'It is set either if there\'s not at least an unreconciled debit and an unreconciled credit ' 'or if you click the "Done" button.') invoice_ids = fields.One2many('account.move', 'partner_id', string='Invoices', readonly=True, copy=False) contract_ids = fields.One2many('account.analytic.account', 'partner_id', string='Partner Contracts', readonly=True) bank_account_count = fields.Integer(compute='_compute_bank_count', string="Bank") trust = fields.Selection([('good', 'Good Debtor'), ('normal', 'Normal Debtor'), ('bad', 'Bad Debtor')], string='Degree of trust you have in this debtor', default='normal', company_dependent=True) invoice_warn = fields.Selection(WARNING_MESSAGE, 'Invoice', help=WARNING_HELP, default="no-message") invoice_warn_msg = fields.Text('Message for Invoice') # Computed fields to order the partners as suppliers/customers according to the # amount of their generated incoming/outgoing account moves supplier_rank = fields.Integer(default=0) customer_rank = fields.Integer(default=0) def _get_name_search_order_by_fields(self): res = super()._get_name_search_order_by_fields() partner_search_mode = self.env.context.get('res_partner_search_mode') if not partner_search_mode in ('customer', 'supplier'): return res order_by_field = 'COALESCE(res_partner.%s, 0) DESC,' if partner_search_mode == 'customer': field = 'customer_rank' else: field = 'supplier_rank' order_by_field = order_by_field % field return '%s, %s' % (res, order_by_field % field) if res else order_by_field def _compute_bank_count(self): bank_data = self.env['res.partner.bank'].read_group( [('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id']) mapped_data = dict([(bank['partner_id'][0], bank['partner_id_count']) for bank in bank_data]) for partner in self: partner.bank_account_count = mapped_data.get(partner.id, 0) def _find_accounting_partner(self, partner): ''' Find the partner for which the accounting entries will be created ''' return partner.commercial_partner_id @api.model def _commercial_fields(self): return super(ResPartner, self)._commercial_fields() + \ ['debit_limit', 'property_account_payable_id', 'property_account_receivable_id', 'property_account_position_id', 'property_payment_term_id', 'property_supplier_payment_term_id', 'last_time_entries_checked'] def action_view_partner_invoices(self): self.ensure_one() action = self.env.ref('account.action_move_out_invoice_type').read()[0] action['domain'] = [ ('type', 'in', ('out_invoice', 'out_refund')), ('partner_id', 'child_of', self.id), ] action['context'] = { 'default_type': 'out_invoice', 'type': 'out_invoice', 'journal_type': 'sale', 'search_default_unpaid': 1 } return action @api.onchange('company_id', 'parent_id') def _onchange_company_id(self): super(ResPartner, self)._onchange_company_id() if self.company_id: company = self.company_id else: company = self.env.company return { 'domain': { 'property_account_position_id': [('company_id', 'in', [company.id, False])] } } def can_edit_vat(self): ''' Can't edit `vat` if there is (non draft) issued invoices. ''' can_edit_vat = super(ResPartner, self).can_edit_vat() if not can_edit_vat: return can_edit_vat has_invoice = self.env['account.move'].search( [('type', 'in', ['out_invoice', 'out_refund']), ('partner_id', 'child_of', self.commercial_partner_id.id), ('state', '=', 'posted')], limit=1) return can_edit_vat and not (bool(has_invoice)) @api.model_create_multi def create(self, vals_list): search_partner_mode = self.env.context.get('res_partner_search_mode') is_customer = search_partner_mode == 'customer' is_supplier = search_partner_mode == 'supplier' if search_partner_mode: for vals in vals_list: if is_customer and 'customer_rank' not in vals: vals['customer_rank'] = 1 elif is_supplier and 'supplier_rank' not in vals: vals['supplier_rank'] = 1 return super().create(vals_list) def _increase_rank(self, field): if self.ids and field in ['customer_rank', 'supplier_rank']: try: with self.env.cr.savepoint(): query = sql.SQL(""" SELECT {field} FROM res_partner WHERE ID IN %(partner_ids)s FOR UPDATE NOWAIT; UPDATE res_partner SET {field} = {field} + 1 WHERE id IN %(partner_ids)s """).format(field=sql.Identifier(field)) self.env.cr.execute(query, {'partner_ids': tuple(self.ids)}) for partner in self: self.env.cache.remove(partner, partner._fields[field]) except DatabaseError as e: if e.pgcode == '55P03': _logger.debug( 'Another transaction already locked partner rows. Cannot update partner ranks.' ) else: raise e
class TaxAdjustments(models.TransientModel): _name = 'tax.adjustments.wizard' _description = 'Tax Adjustments Wizard' def _get_default_journal(self): return self.env['account.journal'].search([('type', '=', 'general')], limit=1).id reason = fields.Char(string='Justification', required=True) journal_id = fields.Many2one('account.journal', string='Journal', required=True, default=_get_default_journal, domain=[('type', '=', 'general')]) date = fields.Date(required=True, default=fields.Date.context_today) debit_account_id = fields.Many2one('account.account', string='Debit account', required=True, domain=[('deprecated', '=', False)]) credit_account_id = fields.Many2one('account.account', string='Credit account', required=True, domain=[('deprecated', '=', False)]) amount = fields.Monetary(currency_field='company_currency_id', required=True) adjustment_type = fields.Selection( [('debit', 'Applied on debit journal item'), ('credit', 'Applied on credit journal item')], string="Adjustment Type", required=True) tax_report_line_id = fields.Many2one( string="Report Line", comodel_name='account.tax.report.line', required=True, help="The report line to make an adjustment for.") company_currency_id = fields.Many2one( 'res.currency', readonly=True, default=lambda x: x.env.company.currency_id) country_id = fields.Many2one(string="Country", comodel_name='res.country', readonly=True, default=lambda x: x.env.company.country_id) def create_move(self): move_line_vals = [] is_debit = self.adjustment_type == 'debit' sign_multiplier = (self.amount < 0 and -1 or 1) * (self.adjustment_type == 'credit' and -1 or 1) filter_lambda = (sign_multiplier < 0) and (lambda x: x.tax_negate) or ( lambda x: not x.tax_negate) adjustment_tag = self.tax_report_line_id.tag_ids.filtered( filter_lambda) # Vals for the amls corresponding to the ajustment tag move_line_vals.append((0, 0, { 'name': self.reason, 'debit': is_debit and abs(self.amount) or 0, 'credit': not is_debit and abs(self.amount) or 0, 'account_id': is_debit and self.debit_account_id.id or self.credit_account_id.id, 'tag_ids': [(6, False, [adjustment_tag.id])], })) # Vals for the counterpart line move_line_vals.append((0, 0, { 'name': self.reason, 'debit': not is_debit and abs(self.amount) or 0, 'credit': is_debit and abs(self.amount) or 0, 'account_id': is_debit and self.credit_account_id.id or self.debit_account_id.id, })) # Create the move vals = { 'journal_id': self.journal_id.id, 'date': self.date, 'state': 'draft', 'line_ids': move_line_vals, } move = self.env['account.move'].create(vals) move.post() # Return an action opening the created move action = self.env.ref( self.env.context.get('action', 'account.action_move_line_form')) result = action.read()[0] result['views'] = [(False, 'form')] result['res_id'] = move.id return result
class HrExpenseSheetRegisterPaymentWizard(models.TransientModel): _name = "hr.expense.sheet.register.payment.wizard" _description = "Expense Register Payment Wizard" @api.model def default_get(self, fields): result = super(HrExpenseSheetRegisterPaymentWizard, self).default_get(fields) active_model = self._context.get('active_model') if active_model != 'hr.expense.sheet': raise UserError( _('You can only apply this action from an expense report.')) active_id = self._context.get('active_id') if 'expense_sheet_id' in fields and active_id: result['expense_sheet_id'] = active_id if 'partner_id' in fields and active_id and not result.get( 'partner_id'): expense_sheet = self.env['hr.expense.sheet'].browse(active_id) result[ 'partner_id'] = expense_sheet.address_id.id or expense_sheet.employee_id.id and expense_sheet.employee_id.address_home_id.id return result expense_sheet_id = fields.Many2one('hr.expense.sheet', string="Expense Report", required=True) partner_id = fields.Many2one( 'res.partner', string='Partner', required=True, domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") partner_bank_account_id = fields.Many2one( 'res.partner.bank', string="Recipient Bank Account", domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") journal_id = fields.Many2one( 'account.journal', string='Payment Method', required=True, domain= "[('type', 'in', ('bank', 'cash')), ('company_id', '=', company_id)]") company_id = fields.Many2one('res.company', related='expense_sheet_id.company_id', string='Company', readonly=True) payment_method_id = fields.Many2one('account.payment.method', string='Payment Type', required=True) amount = fields.Monetary(string='Payment Amount', required=True) currency_id = fields.Many2one( 'res.currency', string='Currency', required=True, default=lambda self: self.env.company.currency_id) payment_date = fields.Date(string='Payment Date', default=fields.Date.context_today, required=True) communication = fields.Char(string='Memo') 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'" ) show_partner_bank_account = fields.Boolean( compute='_compute_show_partner_bank', help= 'Technical field used to know whether the field `partner_bank_account_id` needs to be displayed or not in the payments form views' ) require_partner_bank_account = fields.Boolean( compute='_compute_show_partner_bank', help= 'Technical field used to know whether the field `partner_bank_account_id` needs to be required or not in the payments form views' ) @api.onchange('partner_id') def _onchange_partner_id(self): expense_sheet = self.expense_sheet_id if expense_sheet.employee_id.id and expense_sheet.employee_id.sudo( ).bank_account_id.id: self.partner_bank_account_id = expense_sheet.employee_id.sudo( ).bank_account_id.id elif self.partner_id and len(self.partner_id.bank_ids) > 0: self.partner_bank_account_id = self.partner_id.bank_ids[0] else: self.partner_bank_account_id = False @api.constrains('amount') def _check_amount(self): for wizard in self: if not wizard.amount > 0.0: raise ValidationError( _('The payment amount must be strictly positive.')) @api.depends('payment_method_id') def _compute_show_partner_bank(self): """ Computes if the destination bank account must be displayed in the payment form view. By default, it won't be displayed but some modules might change that, depending on the payment type.""" for payment in self: payment.show_partner_bank_account = payment.payment_method_id.code in self.env[ 'account.payment']._get_method_codes_using_bank_account() payment.require_partner_bank_account = payment.payment_method_id.code in self.env[ 'account.payment']._get_method_codes_needing_bank_account() @api.depends('journal_id') def _compute_hide_payment_method(self): for wizard in self: if not wizard.journal_id: wizard.hide_payment_method = True else: journal_payment_methods = wizard.journal_id.outbound_payment_method_ids wizard.hide_payment_method = ( len(journal_payment_methods) == 1 and journal_payment_methods[0].code == 'manual') @api.onchange('journal_id') def _onchange_journal(self): if self.journal_id: # Set default payment method (we consider the first to be the default one) payment_methods = self.journal_id.outbound_payment_method_ids self.payment_method_id = payment_methods and payment_methods[ 0] or False # Set payment method domain (restrict to methods enabled for the journal and to selected payment type) return { 'domain': { 'payment_method_id': [('payment_type', '=', 'outbound'), ('id', 'in', payment_methods.ids)] } } return {} def _get_payment_vals(self): """ Hook for extension """ return { 'partner_type': 'supplier', 'payment_type': 'outbound', 'partner_id': self.partner_id.id, 'partner_bank_account_id': self.partner_bank_account_id.id, 'journal_id': self.journal_id.id, 'company_id': self.company_id.id, 'payment_method_id': self.payment_method_id.id, 'amount': self.amount, 'currency_id': self.currency_id.id, 'payment_date': self.payment_date, 'communication': self.communication } def expense_post_payment(self): self.ensure_one() company = self.company_id self = self.with_context(force_company=company.id, company_id=company.id) context = dict(self._context or {}) active_ids = context.get('active_ids', []) expense_sheet = self.env['hr.expense.sheet'].browse(active_ids) # Create payment and post it payment = self.env['account.payment'].create(self._get_payment_vals()) payment.post() # Log the payment in the chatter body = (_( "A payment of %s %s with the reference <a href='/mail/view?%s'>%s</a> related to your expense %s has been made." ) % (payment.amount, payment.currency_id.symbol, url_encode({ 'model': 'account.payment', 'res_id': payment.id }), payment.name, expense_sheet.name)) expense_sheet.message_post(body=body) # Reconcile the payment and the expense, i.e. lookup on the payable account move lines account_move_lines_to_reconcile = self.env['account.move.line'] for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids: if line.account_id.internal_type == 'payable' and not line.reconciled: account_move_lines_to_reconcile |= line account_move_lines_to_reconcile.reconcile() return {'type': 'ir.actions.act_window_close'}
class MonetaryBase(models.Model): _name = 'test_new_api.monetary_base' _description = 'Monetary Base' base_currency_id = fields.Many2one('res.currency') amount = fields.Monetary(currency_field='base_currency_id')
class ProjectCreateInvoice(models.TransientModel): _name = 'project.create.invoice' _description = "Create Invoice from project" @api.model def default_get(self, fields): result = super(ProjectCreateInvoice, self).default_get(fields) active_model = self._context.get('active_model') if active_model != 'project.project': raise UserError( _('You can only apply this action from a project.')) active_id = self._context.get('active_id') if 'project_id' in fields and active_id: result['project_id'] = active_id return result project_id = fields.Many2one('project.project', "Project", help="Project to make billable", required=True) sale_order_id = fields.Many2one('sale.order', string="Choose the Sales Order to invoice", required=True) amount_to_invoice = fields.Monetary( "Amount to invoice", compute='_compute_amount_to_invoice', currency_field='currency_id', help= "Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)" ) currency_id = fields.Many2one(related='sale_order_id.currency_id', readonly=True) @api.onchange('project_id') def _onchange_project_id(self): sale_orders = self.project_id.tasks.mapped( 'sale_line_id.order_id').filtered( lambda so: so.invoice_status == 'to invoice') return { 'domain': { 'sale_order_id': [('id', 'in', sale_orders.ids)] }, } @api.depends('sale_order_id') def _compute_amount_to_invoice(self): for wizard in self: amount_untaxed = 0.0 amount_tax = 0.0 for line in wizard.sale_order_id.order_line.filtered( lambda sol: sol.invoice_status == 'to invoice'): amount_untaxed += line.price_reduce * line.qty_to_invoice amount_tax += line.price_tax wizard.amount_to_invoice = amount_untaxed + amount_tax def action_create_invoice(self): if not self.sale_order_id and self.sale_order_id.invoice_status != 'to invoice': raise UserError( _("The selected Sales Order should contain something to invoice." )) action = self.env.ref( 'sale.action_view_sale_advance_payment_inv').read()[0] action['context'] = {'active_ids': self.sale_order_id.ids} return action
class PaymentLinkWizard(models.TransientModel): _name = "payment.link.wizard" _description = "Generate Payment Link" @api.model def default_get(self, fields): res = super(PaymentLinkWizard, self).default_get(fields) res_id = self._context.get('active_id') res_model = self._context.get('active_model') res.update({'res_id': res_id, 'res_model': res_model}) amount_field = 'amount_residual' if res_model == 'account.move' else 'amount_total' if res_id and res_model == 'account.move': record = self.env[res_model].browse(res_id) res.update({ 'description': record.invoice_payment_ref, 'amount': record[amount_field], 'currency_id': record.currency_id.id, 'partner_id': record.partner_id.id, 'amount_max': record[amount_field], }) return res res_model = fields.Char('Related Document Model', required=True) res_id = fields.Integer('Related Document ID', required=True) amount = fields.Monetary(currency_field='currency_id', required=True) amount_max = fields.Monetary(currency_field='currency_id') currency_id = fields.Many2one('res.currency') partner_id = fields.Many2one('res.partner') partner_email = fields.Char(related='partner_id.email') link = fields.Char(string='Payment Link', compute='_compute_values') description = fields.Char('Payment Ref') access_token = fields.Char(compute='_compute_values') company_id = fields.Many2one('res.company', compute='_compute_company') @api.onchange('amount', 'description') def _onchange_amount(self): if float_compare(self.amount_max, self.amount, precision_rounding=self.currency_id.rounding or 0.01) == -1: raise ValidationError( _("Please set an amount smaller than %s.") % (self.amount_max)) if self.amount <= 0: raise ValidationError( _("The value of the payment amount must be positive.")) @api.depends('amount', 'description', 'partner_id', 'currency_id') def _compute_values(self): secret = self.env['ir.config_parameter'].sudo().get_param( 'database.secret') for payment_link in self: token_str = '%s%s%s' % (payment_link.partner_id.id, payment_link.amount, payment_link.currency_id.id) payment_link.access_token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest() # must be called after token generation, obvsly - the link needs an up-to-date token self._generate_link() @api.depends('res_model', 'res_id') def _compute_company(self): for link in self: record = self.env[link.res_model].browse(link.res_id) link.company_id = record.company_id if 'company_id' in record else False def _generate_link(self): base_url = self.env['ir.config_parameter'].sudo().get_param( 'web.base.url') for payment_link in self: link = ( '%s/website_payment/pay?reference=%s&amount=%s¤cy_id=%s' '&partner_id=%s&access_token=%s') % ( base_url, urls.url_quote(payment_link.description), payment_link.amount, payment_link.currency_id.id, payment_link.partner_id.id, payment_link.access_token) if payment_link.company_id: link += '&company_id=%s' % payment_link.company_id.id payment_link.link = link @api.model def check_token(self, access_token, partner_id, amount, currency_id): secret = self.env['ir.config_parameter'].sudo().get_param( 'database.secret') token_str = '%s%s%s' % (partner_id, amount, currency_id) correct_token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest() if consteq(ustr(access_token), correct_token): return True return False
class AccrualAccountingWizard(models.TransientModel): _name = 'account.accrual.accounting.wizard' _description = 'Create accrual entry.' date = fields.Date(required=True) company_id = fields.Many2one('res.company', required=True) account_type = fields.Selection([('income', 'Revenue'), ('expense', 'Expense')]) active_move_line_ids = fields.Many2many('account.move.line') journal_id = fields.Many2one( 'account.journal', required=True, readonly=False, domain="[('company_id', '=', company_id), ('type', '=', 'general')]", related="company_id.accrual_default_journal_id") expense_accrual_account = fields.Many2one( 'account.account', readonly=False, domain= "[('company_id', '=', company_id), ('internal_type', 'not in', ('receivable', 'payable')), ('internal_group', '=', 'liability'), ('reconcile', '=', True)]", related="company_id.expense_accrual_account_id") revenue_accrual_account = fields.Many2one( 'account.account', readonly=False, domain= "[('company_id', '=', company_id), ('internal_type', 'not in', ('receivable', 'payable')), ('internal_group', '=', 'asset'), ('reconcile', '=', True)]", related="company_id.revenue_accrual_account_id") percentage = fields.Float("Percentage", default=100.0) total_amount = fields.Monetary(compute="_compute_total_amount", currency_field='company_currency_id') company_currency_id = fields.Many2one('res.currency', related='company_id.currency_id') @api.constrains('percentage') def _constraint_percentage(self): for record in self: if not (0.0 < record.percentage <= 100.0): raise UserError(_("Percentage must be between 0 and 100")) @api.depends('percentage', 'active_move_line_ids') def _compute_total_amount(self): for record in self: record.total_amount = sum( record.active_move_line_ids.mapped( lambda l: record.percentage * (l.debit + l.credit) / 100)) @api.model def default_get(self, fields): if self.env.context.get( 'active_model' ) != 'account.move.line' or not self.env.context.get('active_ids'): raise UserError(_('This can only be used on journal items')) rec = super(AccrualAccountingWizard, self).default_get(fields) active_move_line_ids = self.env['account.move.line'].browse( self.env.context['active_ids']) rec['active_move_line_ids'] = active_move_line_ids.ids if any(move.state != 'posted' for move in active_move_line_ids.mapped('move_id')): raise UserError( _('You can only change the period for posted journal items.')) if any(move_line.reconciled for move_line in active_move_line_ids): raise UserError( _('You can only change the period for items that are not yet reconciled.' )) if any(line.account_id.user_type_id != active_move_line_ids[0].account_id.user_type_id for line in active_move_line_ids): raise UserError( _('All accounts on the lines must be from the same type.')) if any(line.company_id != active_move_line_ids[0].company_id for line in active_move_line_ids): raise UserError(_('All lines must be from the same company.')) rec['company_id'] = active_move_line_ids[0].company_id.id account_types_allowed = self.env.ref( 'account.data_account_type_expenses') + self.env.ref( 'account.data_account_type_revenue') + self.env.ref( 'account.data_account_type_other_income') if active_move_line_ids[ 0].account_id.user_type_id not in account_types_allowed: raise UserError( _('You can only change the period for items in these types of accounts: ' ) + ", ".join(account_types_allowed.mapped('name'))) rec['account_type'] = active_move_line_ids[ 0].account_id.user_type_id.internal_group return rec def amend_entries(self): # set the accrual account on the selected journal items accrual_account = self.revenue_accrual_account if self.account_type == 'income' else self.expense_accrual_account # Generate journal entries. move_data = {} for aml in self.active_move_line_ids: ref1 = _( 'Accrual Adjusting Entry (%s%% recognized) for invoice: %s' ) % (self.percentage, aml.move_id.name) ref2 = _( 'Accrual Adjusting Entry (%s%% recognized) for invoice: %s' ) % (100 - self.percentage, aml.move_id.name) move_data.setdefault( aml.move_id, ( [ # Values to create moves. { 'date': self.date, 'ref': ref1, 'journal_id': self.journal_id.id, 'line_ids': [], }, { 'date': aml.move_id.date, 'ref': ref2, 'journal_id': self.journal_id.id, 'line_ids': [], }, ], [ # Messages to log on the chatter. (_('Accrual Adjusting Entry ({percent}% recognized) for invoice:' ) + ' <a href=# data-oe-model=account.move data-oe-id={id}>{name}</a>' ).format( percent=self.percentage, id=aml.move_id.id, name=aml.move_id.name, ), (_('Accrual Adjusting Entry ({percent}% recognized) for invoice:' ) + ' <a href=# data-oe-model=account.move data-oe-id={id}>{name}</a>' ).format( percent=100 - self.percentage, id=aml.move_id.id, name=aml.move_id.name, ), (_('Accrual Adjusting Entries ({percent}%% recognized) have been created for this invoice on {date}' ) + ' <a href=# data-oe-model=account.move data-oe-id=%(first_id)d>%(first_name)s</a> and <a href=# data-oe-model=account.move data-oe-id=%(second_id)d>%(second_name)s</a>' ).format( percent=self.percentage, date=format_date(self.env, self.date), ), ])) reported_debit = aml.company_id.currency_id.round( (self.percentage / 100) * aml.debit) reported_credit = aml.company_id.currency_id.round( (self.percentage / 100) * aml.credit) if aml.currency_id: reported_amount_currency = aml.currency_id.round( (self.percentage / 100) * aml.amount_currency) else: reported_amount_currency = 0.0 move_data[aml.move_id][0][0]['line_ids'] += [ (0, 0, { 'name': aml.name, 'debit': reported_debit, 'credit': reported_credit, 'amount_currency': reported_amount_currency, 'currency_id': aml.currency_id.id, 'account_id': aml.account_id.id, 'partner_id': aml.partner_id.id, }), (0, 0, { 'name': ref1, 'debit': reported_credit, 'credit': reported_debit, 'amount_currency': -reported_amount_currency, 'currency_id': aml.currency_id.id, 'account_id': accrual_account.id, 'partner_id': aml.partner_id.id, }), ] move_data[aml.move_id][0][1]['line_ids'] += [ (0, 0, { 'name': aml.name, 'debit': reported_credit, 'credit': reported_debit, 'amount_currency': -reported_amount_currency, 'currency_id': aml.currency_id.id, 'account_id': aml.account_id.id, 'partner_id': aml.partner_id.id, }), (0, 0, { 'name': ref2, 'debit': reported_debit, 'credit': reported_credit, 'amount_currency': reported_amount_currency, 'currency_id': aml.currency_id.id, 'account_id': accrual_account.id, 'partner_id': aml.partner_id.id, }), ] move_vals = [] log_messages = [] for v in move_data.values(): move_vals += v[0] log_messages += v[1] created_moves = self.env['account.move'].create(move_vals) created_moves.post() # Reconcile. index = 0 for move in self.active_move_line_ids.mapped('move_id'): accrual_moves = created_moves[index:index + 2] to_reconcile = accrual_moves.mapped('line_ids').filtered( lambda line: line.account_id == accrual_account) to_reconcile.reconcile() move.message_post( body=log_messages[index // 2 + 2] % { 'first_id': accrual_moves[0].id, 'first_name': accrual_moves[0].name, 'second_id': accrual_moves[1].id, 'second_name': accrual_moves[1].name, }) accrual_moves[0].message_post(body=log_messages[index // 2 + 0]) accrual_moves[1].message_post(body=log_messages[index // 2 + 1]) index += 2 # open the generated entries action = { 'name': _('Generated Entries'), 'domain': [('id', 'in', created_moves.ids)], 'res_model': 'account.move', 'view_mode': 'tree,form', 'type': 'ir.actions.act_window', 'views': [(self.env.ref('account.view_move_tree').id, 'tree'), (False, 'form')], } if len(created_moves) == 1: action.update({'view_mode': 'form', 'res_id': created_moves.id}) return action
class SaleOrder(models.Model): _inherit = 'sale.order' amount_delivery = fields.Monetary(compute='_compute_amount_delivery', string='Delivery Amount', help="The amount without tax.", store=True, tracking=True) def _compute_website_order_line(self): super(SaleOrder, self)._compute_website_order_line() for order in self: order.website_order_line = order.website_order_line.filtered( lambda l: not l.is_delivery) @api.depends('order_line.price_unit', 'order_line.tax_id', 'order_line.discount', 'order_line.product_uom_qty') def _compute_amount_delivery(self): for order in self: if self.env.user.has_group( 'account.group_show_line_subtotals_tax_excluded'): order.amount_delivery = sum( order.order_line.filtered('is_delivery').mapped( 'price_subtotal')) else: order.amount_delivery = sum( order.order_line.filtered('is_delivery').mapped( 'price_total')) def _check_carrier_quotation(self, force_carrier_id=None): self.ensure_one() DeliveryCarrier = self.env['delivery.carrier'] if self.only_services: self.write({'carrier_id': None}) self._remove_delivery_line() return True else: # attempt to use partner's preferred carrier if not force_carrier_id and self.partner_shipping_id.property_delivery_carrier_id: force_carrier_id = self.partner_shipping_id.property_delivery_carrier_id.id carrier = force_carrier_id and DeliveryCarrier.browse( force_carrier_id) or self.carrier_id available_carriers = self._get_delivery_methods() if carrier: if carrier not in available_carriers: carrier = DeliveryCarrier else: # set the forced carrier at the beginning of the list to be verfied first below available_carriers -= carrier available_carriers = carrier + available_carriers if force_carrier_id or not carrier or carrier not in available_carriers: for delivery in available_carriers: verified_carrier = delivery._match_address( self.partner_shipping_id) if verified_carrier: carrier = delivery break self.write({'carrier_id': carrier.id}) self._remove_delivery_line() if carrier: res = carrier.rate_shipment(self) if res.get('success'): self.set_delivery_line(carrier, res['price']) self.delivery_rating_success = True self.delivery_message = res['warning_message'] else: self.set_delivery_line(carrier, 0.0) self.delivery_rating_success = False self.delivery_message = res['error_message'] return bool(carrier) def _get_delivery_methods(self): address = self.partner_shipping_id # searching on website_published will also search for available website (_search method on computed field) return self.env['delivery.carrier'].sudo().search([ ('website_published', '=', True) ]).available_carriers(address) def _cart_update(self, product_id=None, line_id=None, add_qty=0, set_qty=0, **kwargs): """ Override to update carrier quotation if quantity changed """ self._remove_delivery_line() # When you update a cart, it is not enouf to remove the "delivery cost" line # The carrier might also be invalid, eg: if you bought things that are too heavy # -> this may cause a bug if you go to the checkout screen, choose a carrier, # then update your cart (the cart becomes uneditable) self.write({'carrier_id': False}) values = super(SaleOrder, self)._cart_update(product_id, line_id, add_qty, set_qty, **kwargs) return values
class StockValuationLayer(models.Model): """Stock Valuation Layer""" _name = 'stock.valuation.layer' _description = 'Stock Valuation Layer' _order = 'create_date, id' _rec_name = 'product_id' active = fields.Boolean(related='product_id.active') company_id = fields.Many2one('res.company', 'Company', readonly=True, required=True) product_id = fields.Many2one('product.product', 'Product', readonly=True, required=True, check_company=True) categ_id = fields.Many2one('product.category', related='product_id.categ_id') product_tmpl_id = fields.Many2one('product.template', related='product_id.product_tmpl_id') quantity = fields.Float('Quantity', digits=0, help='Quantity', readonly=True) uom_id = fields.Many2one(related='product_id.uom_id', readonly=True, required=True) currency_id = fields.Many2one('res.currency', 'Currency', related='company_id.currency_id', readonly=True, required=True) unit_cost = fields.Monetary('Unit Value', readonly=True) value = fields.Monetary('Total Value', readonly=True) remaining_qty = fields.Float(digits=0, readonly=True) remaining_value = fields.Monetary('Remaining Value', readonly=True) description = fields.Char('Description', readonly=True) stock_valuation_layer_id = fields.Many2one('stock.valuation.layer', 'Linked To', readonly=True, check_company=True) stock_valuation_layer_ids = fields.One2many('stock.valuation.layer', 'stock_valuation_layer_id') stock_move_id = fields.Many2one('stock.move', 'Stock Move', readonly=True, check_company=True, index=True) account_move_id = fields.Many2one('account.move', 'Journal Entry', readonly=True, check_company=True) def init(self): tools.create_index(self._cr, 'stock_valuation_layer_index', self._table, [ 'product_id', 'remaining_qty', 'stock_move_id', 'company_id', 'create_date' ])
class ProductWishlist(models.Model): _name = 'product.wishlist' _description = 'Product Wishlist' _sql_constraints = [ ("product_unique_partner_id", "UNIQUE(product_id, partner_id)", "Duplicated wishlisted product for this partner."), ] partner_id = fields.Many2one('res.partner', string='Owner') product_id = fields.Many2one('product.product', string='Product', required=True) currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', readonly=True) pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', help='Pricelist when added') price = fields.Monetary( currency_field='currency_id', string='Price', help='Price of the product when it has been added in the wishlist') website_id = fields.Many2one('website', ondelete='cascade', required=True) active = fields.Boolean(default=True, required=True) @api.model def current(self): """Get all wishlist items that belong to current user or session, filter products that are unpublished.""" if not request: return self if request.website.is_public_user(): wish = self.sudo().search([ ('id', 'in', request.session.get('wishlist_ids', [])) ]) else: wish = self.search([("partner_id", "=", self.env.user.partner_id.id), ('website_id', '=', request.website.id)]) return wish.filtered( lambda x: x.sudo().product_id.product_tmpl_id.website_published) @api.model def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False): wish = self.env['product.wishlist'].create({ 'partner_id': partner_id, 'product_id': product_id, 'currency_id': currency_id, 'pricelist_id': pricelist_id, 'price': price, 'website_id': website_id, }) return wish @api.model def _check_wishlist_from_session(self): """Assign all wishlist withtout partner from this the current session""" session_wishes = self.sudo().search([ ('id', 'in', request.session.get('wishlist_ids', [])) ]) partner_wishes = self.sudo().search([("partner_id", "=", self.env.user.partner_id.id)]) partner_products = partner_wishes.mapped("product_id") # Remove session products already present for the user duplicated_wishes = session_wishes.filtered( lambda wish: wish.product_id <= partner_products) session_wishes -= duplicated_wishes duplicated_wishes.unlink() # Assign the rest to the user session_wishes.write({"partner_id": self.env.user.partner_id.id}) request.session.pop('wishlist_ids') @api.model def _garbage_collector(self, *args, **kwargs): """Remove wishlists for unexisting sessions.""" self.with_context(active_test=False).search([ ("create_date", "<", fields.Datetime.to_string(datetime.now() - timedelta( weeks=kwargs.get('wishlist_week', 5)))), ("partner_id", "=", False), ]).unlink()
class AccountAnalyticLine(models.Model): _name = 'account.analytic.line' _description = 'Analytic Line' _order = 'date desc, id desc' @api.model def _default_user(self): return self.env.context.get('user_id', self.env.user.id) name = fields.Char('Description', required=True) date = fields.Date('Date', required=True, index=True, default=fields.Date.context_today) amount = fields.Monetary('Amount', required=True, default=0.0) unit_amount = fields.Float('Quantity', default=0.0) product_uom_id = fields.Many2one( 'uom.uom', string='Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]") product_uom_category_id = fields.Many2one( related='product_uom_id.category_id', readonly=True) account_id = fields.Many2one( 'account.analytic.account', 'Analytic Account', required=True, ondelete='restrict', index=True, domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") partner_id = fields.Many2one( 'res.partner', string='Partner', domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") user_id = fields.Many2one('res.users', string='User', default=_default_user) tag_ids = fields.Many2many( 'account.analytic.tag', 'account_analytic_line_tag_rel', 'line_id', 'tag_id', string='Tags', copy=True, domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.company) currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True, store=True, compute_sudo=True) group_id = fields.Many2one('account.analytic.group', related='account_id.group_id', store=True, readonly=True, compute_sudo=True) @api.constrains('company_id', 'account_id') def _check_company_id(self): for line in self: if line.account_id.company_id and line.company_id.id != line.account_id.company_id.id: raise ValidationError( _('The selected account belongs to another company that the one you\'re trying to create an analytic item for' ))