class AccountAnalyticLine(models.Model): _inherit = 'account.analytic.line' _description = 'Analytic Line' _order = 'date desc' product_uom_id = fields.Many2one('product.uom', string='Unit of Measure') product_id = fields.Many2one('product.product', string='Product') general_account_id = fields.Many2one('account.account', string='Financial Account', ondelete='restrict', related='move_id.account_id', store=True, domain=[('deprecated', '=', False)]) move_id = fields.Many2one('account.move.line', string='Move Line', ondelete='cascade', index=True) code = fields.Char(size=8) ref = fields.Char(string='Ref.') currency_id = fields.Many2one('res.currency', related='move_id.currency_id', string='Account Currency', store=True, help="The related account currency if not equal to the company one.", readonly=True) amount_currency = fields.Monetary(related='move_id.amount_currency', store=True, help="The amount expressed in the related account currency if not equal to the company one.", readonly=True) partner_id = fields.Many2one('res.partner', related='account_id.partner_id', string='Partner', store=True) @api.v8 @api.onchange('product_id', 'product_uom_id', 'unit_amount', 'currency_id') def on_change_unit_amount(self): if not self.product_id: return {} result = 0.0 prod_accounts = self.product_id.product_tmpl_id._get_product_accounts() unit = self.product_uom_id account = prod_accounts['expense'] if not unit or self.product_id.uom_po_id.category_id.id != unit.category_id.id: unit = self.product_id.uom_po_id ctx = dict(self._context or {}) if unit: # price_get() will respect a 'uom' in its context, in order # to return a default price for those units ctx['uom'] = unit.id # Compute based on pricetype amount_unit = self.product_id.with_context(ctx).price_get('standard_price')[self.product_id.id] amount = amount_unit * self.unit_amount or 0.0 result = round(amount, self.currency_id.decimal_places) * -1 self.amount = result self.general_account_id = account self.product_uom_id = unit @api.model def view_header_get(self, view_id, view_type): context = (self._context or {}) header = False if context.get('account_id', False): analytic_account = self.env['account.analytic.account'].search([('id', '=', context['account_id'])], limit=1) header = _('Entries: ') + (analytic_account.name or '') return header
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=1.0) 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 account_analytic_line(models.Model): _name = 'account.analytic.line' _description = 'Analytic Line' _order = 'date desc, id desc' @api.model def _default_user(self): return 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) account_id = fields.Many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='restrict', index=True) partner_id = fields.Many2one('res.partner', string='Partner') 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) company_id = fields.Many2one(related='account_id.company_id', string='Company', store=True, readonly=True) currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True)
class ResPartner(models.Model): _name = 'res.partner' _inherit = 'res.partner' _description = 'Partner' @api.multi def _credit_debit_get(self): tables, where_clause, where_params = self.env['account.move.line']._query_get() where_params = [tuple(self.ids)] + where_params self._cr.execute("""SELECT l.partner_id, act.type, SUM(l.amount_residual) FROM account_move_line l LEFT JOIN account_account a ON (l.account_id=a.id) LEFT JOIN account_account_type act ON (a.user_type_id=act.id) WHERE act.type IN ('receivable','payable') AND l.partner_id IN %s AND l.reconciled IS FALSE """ + where_clause + """ GROUP BY l.partner_id, act.type """, where_params) for pid, type, val in self._cr.fetchall(): partner = self.browse(pid) if type == 'receivable': partner.credit = val elif type == 'payable': partner.debit = -val @api.multi def _asset_difference_search(self, type, args): if not args: return [] having_values = tuple(map(itemgetter(2), args)) where = ' AND '.join( map(lambda x: '(SUM(bal2) %(operator)s %%s)' % { 'operator':x[1]},args)) query = self.env['account.move.line']._query_get() self._cr.execute(('SELECT pid AS partner_id, SUM(bal2) FROM ' \ '(SELECT CASE WHEN bal IS NOT NULL THEN bal ' \ 'ELSE 0.0 END AS bal2, p.id as pid FROM ' \ '(SELECT (debit-credit) AS bal, partner_id ' \ 'FROM account_move_line l ' \ 'WHERE account_id IN ' \ '(SELECT id FROM account_account '\ 'WHERE type=%s AND active) ' \ 'AND reconciled IS FALSE ' \ 'AND '+query+') AS l ' \ 'RIGHT JOIN res_partner p ' \ 'ON p.id = partner_id ) AS pl ' \ 'GROUP BY pid HAVING ' + where), (type,) + having_values) res = self._cr.fetchall() if not res: return [('id', '=', '0')] return [('id', 'in', map(itemgetter(0), res))] @api.multi def _credit_search(self, args): return self._asset_difference_search('receivable', args) @api.multi def _debit_search(self, args): return self._asset_difference_search('payable', args) @api.multi def _invoice_total(self): account_invoice_report = self.env['account.invoice.report'] if not self.ids: self.total_invoiced = 0.0 return True user_currency_id = self.env.user.company_id.currency_id.id for partner in self: all_partner_ids = self.search([('id', 'child_of', partner.id)]).ids # 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']), ('company_id', '=', self.env.user.company_id.id) ]) account_invoice_report._apply_ir_rules(where_query, 'read') from_clause, where_clause, where_clause_params = where_query.get_sql() query = """ SELECT SUM(price_total) as total FROM account_invoice_report account_invoice_report WHERE %s """ % where_clause # price_total is in the company currency self.env.cr.execute(query, where_clause_params) partner.total_invoiced = self.env.cr.fetchone()[0] @api.multi def _journal_item_count(self): for partner in self: partner.journal_item_count = self.env['account.move.line'].search_count([('partner_id', '=', partner.id)]) partner.contracts_count = self.env['account.analytic.account'].search_count([('partner_id', '=', partner.id)]) def get_followup_lines_domain(self, date, overdue_only=False, only_unblocked=False): domain = [('reconciled', '=', False), ('account_id.deprecated', '=', False), ('account_id.internal_type', '=', 'receivable'), '|', ('debit', '!=', 0), ('credit', '!=', 0), ('company_id', '=', self.env.user.company_id.id)] if only_unblocked: domain += [('blocked', '=', False)] if self.ids: if 'exclude_given_ids' in self._context: domain += [('partner_id', 'not in', self.ids)] else: domain += [('partner_id', 'in', self.ids)] #adding the overdue lines overdue_domain = ['|', '&', ('date_maturity', '!=', False), ('date_maturity', '<=', date), '&', ('date_maturity', '=', False), ('date', '<=', date)] if overdue_only: domain += overdue_domain return domain @api.multi def _compute_issued_total(self): """ Returns the issued total as will be displayed on partner view """ today = fields.Date.context_today(self) for partner in self: domain = partner.get_followup_lines_domain(today, overdue_only=True) issued_total = 0 for aml in self.env['account.move.line'].search(domain): issued_total += aml.amount_residual partner.issued_total = issued_total @api.one def _compute_has_unreconciled_entries(self): # Avoid useless work if has_unreconciled_entries is not relevant for this partner if not self.active or not self.is_company and self.parent_id: return 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) """, (self.id,)) self.has_unreconciled_entries = self.env.cr.rowcount == 1 @api.multi def mark_as_reconciled(self): return self.write({'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}) @api.one def _get_company_currency(self): if self.company_id: self.currency_id = self.sudo().company_id.currency_id else: self.currency_id = self.env.user.company_id.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, help='Utility field to express amount currency') contracts_count = fields.Integer(compute='_journal_item_count', string="Contracts", type='integer') journal_item_count = fields.Integer(compute='_journal_item_count', string="Journal Items", type="integer") issued_total = fields.Monetary(compute='_compute_issued_total', string="Journal Items") property_account_payable_id = fields.Many2one('account.account', company_dependent=True, string="Account Payable", oldname="property_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", oldname="property_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 will determine taxes and accounts used for the partner.", oldname="property_account_position") property_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True, string ='Customer Payment Term', help="This payment term will be used instead of the default one for sale orders and customer invoices", oldname="property_payment_term") property_supplier_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True, string ='Vendor Payment Term', help="This payment term will be used instead of the default one for purchase orders and vendor bills", oldname="property_supplier_payment_term") ref_company_ids = fields.One2many('res.company', 'partner_id', string='Companies that refers to partner', oldname="ref_companies") 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(oldname='last_reconciliation_date', 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.invoice', 'partner_id', string='Invoices', readonly=True, copy=False) contract_ids = fields.One2many('account.analytic.account', 'partner_id', string='Contracts', readonly=True) bank_account_count = fields.Integer(compute='_compute_bank_count', string="Bank") @api.multi 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']
class account_analytic_account(models.Model): _name = 'account.analytic.account' _inherit = ['mail.thread'] _description = 'Analytic Account' _order = 'code, name asc' @api.multi def _compute_debit_credit_balance(self): analytic_line_obj = self.env['account.analytic.line'] domain = [('account_id', 'in', self.mapped('id'))] 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'])) account_amounts = analytic_line_obj.search_read(domain, ['account_id', 'amount']) account_ids = set([line['account_id'][0] for line in account_amounts]) data_debit = {account_id: 0.0 for account_id in account_ids} data_credit = {account_id: 0.0 for account_id in account_ids} for account_amount in account_amounts: if account_amount['amount'] < 0.0: data_debit[account_amount['account_id'][0]] += account_amount['amount'] else: data_credit[account_amount['account_id'][0]] += account_amount['amount'] 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, track_visibility='onchange') code = fields.Char(string='Reference', index=True, track_visibility='onchange') # FIXME: account_type is probably not necessary anymore, could be removed in v10 account_type = fields.Selection([ ('normal', 'Analytic View') ], string='Type of Account', required=True, default='normal') tag_ids = fields.Many2many('account.analytic.tag', 'account_analytic_account_tag_rel', 'account_id', 'tag_id', string='Tags', copy=True) line_ids = fields.One2many('account.analytic.line', 'account_id', string="Analytic Lines") company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.user.company_id) # use auto_join to speed up name_search call partner_id = fields.Many2one('res.partner', string='Customer', auto_join=True) 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) @api.multi def name_get(self): res = [] for analytic in self: name = analytic.name if analytic.code: name = '['+analytic.code+'] '+name if analytic.partner_id: 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): args = args or [] domain = ['|', ('code', operator, name), ('name', operator, name)] partners = self.env['res.partner'].search([('name', operator, name)], limit=limit) if partners: domain = ['|'] + domain + [('partner_id', 'in', partners.ids)] recs = self.search(domain + args, limit=limit) return recs.name_get()
class account_abstract_payment(models.AbstractModel): _name = "account.abstract.payment" _description = "Contains the logic shared between models which allows to register payments" payment_type = fields.Selection([('outbound', 'Send Money'), ('inbound', 'Receive Money')], string='Payment Type', required=True) payment_method_id = fields.Many2one('account.payment.method', string='Payment Type', required=True, oldname="payment_method") payment_method_code = fields.Char( related='payment_method_id.code', help= "Technical field used to adapt the interface to the payment type selected." ) partner_type = fields.Selection([('customer', 'Customer'), ('supplier', 'Vendor')]) partner_id = fields.Many2one('res.partner', string='Partner') amount = fields.Monetary(string='Payment Amount', required=True) currency_id = fields.Many2one( 'res.currency', string='Currency', required=True, default=lambda self: self.env.user.company_id.currency_id) payment_date = fields.Date(string='Payment Date', default=fields.Date.context_today, required=True, copy=False) communication = fields.Char(string='Memo') journal_id = fields.Many2one('account.journal', string='Payment Method', required=True, domain=[('type', 'in', ('bank', 'cash'))]) company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', 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'" ) @api.one @api.constrains('amount') def _check_amount(self): if not self.amount > 0.0: raise ValidationError( 'The payment amount must be strictly positive.') @api.one @api.depends('payment_type', 'journal_id') def _compute_hide_payment_method(self): if not self.journal_id: self.hide_payment_method = True return journal_payment_methods = self.payment_type == 'inbound' and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids self.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: self.currency_id = self.journal_id.currency_id or self.company_id.currency_id # Set default payment method (we consider the first to be the default one) payment_methods = self.payment_type == 'inbound' and self.journal_id.inbound_payment_method_ids or 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) payment_type = self.payment_type in ( 'outbound', 'transfer') and 'outbound' or 'inbound' return { 'domain': { 'payment_method_id': [('payment_type', '=', payment_type), ('id', 'in', payment_methods.ids)] } } return {} def _get_invoices(self): """ Return the invoices of the payment. Must be overridden """ raise NotImplementedError def _compute_total_invoices_amount(self): """ Compute the sum of the residual of invoices, expressed in the payment currency """ total = 0 payment_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id for inv in self._get_invoices(): total += inv.residual_company_signed if self.company_id and self.company_id.currency_id != payment_currency: total = self.company_id.currency_id.with_context( date=self.payment_date).compute(total, payment_currency) return abs(total)
class account_payment(models.Model): _name = "account.payment" _inherit = 'account.abstract.payment' _description = "Payments" _order = "payment_date desc, name desc" @api.one @api.depends('invoice_ids') def _get_has_invoices(self): self.has_invoices = bool(self.invoice_ids) @api.one @api.depends('invoice_ids', 'amount', 'payment_date', 'currency_id') def _compute_payment_difference(self): if len(self.invoice_ids) == 0: return self.payment_difference = self._compute_total_invoices_amount( ) - self.amount company_id = fields.Many2one(store=True) name = fields.Char( readonly=True, copy=False, default="Draft Payment") # The name is attributed upon post() state = fields.Selection([('draft', 'Draft'), ('posted', 'Posted'), ('sent', 'Sent'), ('reconciled', 'Reconciled')], readonly=True, default='draft', copy=False, string="Status") payment_type = fields.Selection(selection_add=[('transfer', 'Internal Transfer')]) payment_reference = fields.Char( copy=False, readonly=True, help= "Reference of the document used to issue this payment. Eg. check number, file name, etc." ) # Money flows from the journal_id's default_debit_account_id or default_credit_account_id to the destination_account_id destination_account_id = fields.Many2one( 'account.account', compute='_compute_destination_account_id', readonly=True) # For money transfer, money goes from journal_id to a transfer account, then from the transfer account to destination_journal_id destination_journal_id = fields.Many2one('account.journal', string='Transfer To', domain=[('type', 'in', ('bank', 'cash'))]) invoice_ids = fields.Many2many('account.invoice', 'account_invoice_payment_rel', 'payment_id', 'invoice_id', string="Invoices", copy=False, readonly=True) has_invoices = fields.Boolean( compute="_get_has_invoices", help="Technical field used for usability purposes") payment_difference = fields.Monetary(compute='_compute_payment_difference', readonly=True) payment_difference_handling = fields.Selection( [('open', 'Keep open'), ('reconcile', 'Mark invoice as fully paid')], default='open', string="Payment Difference", copy=False) writeoff_account_id = fields.Many2one('account.account', string="Difference Account", domain=[('deprecated', '=', False)], copy=False) # FIXME: ondelete='restrict' not working (eg. cancel a bank statement reconciliation with a payment) move_line_ids = fields.One2many('account.move.line', 'payment_id', readonly=True, copy=False, ondelete='restrict') @api.one @api.depends('invoice_ids', 'payment_type', 'partner_type', 'partner_id') def _compute_destination_account_id(self): if self.invoice_ids: self.destination_account_id = self.invoice_ids[0].account_id.id elif self.payment_type == 'transfer': if not self.company_id.transfer_account_id.id: raise UserError( _('Transfer account not defined on the company.')) self.destination_account_id = self.company_id.transfer_account_id.id elif self.partner_id: if self.partner_type == 'customer': self.destination_account_id = self.partner_id.property_account_receivable_id.id else: self.destination_account_id = self.partner_id.property_account_payable_id.id @api.onchange('partner_type') def _onchange_partner_type(self): # Set partner_id domain if self.partner_type: return {'domain': {'partner_id': [(self.partner_type, '=', True)]}} @api.onchange('payment_type') def _onchange_payment_type(self): if not self.invoice_ids: # Set default partner type for the payment type if self.payment_type == 'inbound': self.partner_type = 'customer' elif self.payment_type == 'outbound': self.partner_type = 'supplier' # Set payment method domain res = self._onchange_journal() if not res.get('domain', {}): res['domain'] = {} res['domain']['journal_id'] = self.payment_type == 'inbound' and [ ('at_least_one_inbound', '=', True) ] or [('at_least_one_outbound', '=', True)] res['domain']['journal_id'].append(('type', 'in', ('bank', 'cash'))) return res @api.model def default_get(self, fields): rec = super(account_payment, self).default_get(fields) invoice_defaults = self.resolve_2many_commands('invoice_ids', rec.get('invoice_ids')) if invoice_defaults and len(invoice_defaults) == 1: invoice = invoice_defaults[0] rec['communication'] = invoice['reference'] rec['currency_id'] = invoice['currency_id'][0] rec['payment_type'] = invoice['type'] in ( 'out_invoice', 'in_refund') and 'inbound' or 'outbound' rec['partner_type'] = MAP_INVOICE_TYPE_PARTNER_TYPE[ invoice['type']] rec['partner_id'] = invoice['partner_id'][0] rec['amount'] = invoice['residual'] return rec def _get_invoices(self): return self.invoice_ids @api.multi def button_journal_entries(self): return { 'name': _('Journal Items'), 'view_type': 'form', 'view_mode': 'tree', 'res_model': 'account.move.line', 'view_id': False, 'type': 'ir.actions.act_window', 'domain': [('payment_id', 'in', self.ids)], } @api.multi def button_invoices(self): return { 'name': _('Paid Invoices'), 'view_type': 'form', 'view_mode': 'tree', 'res_model': 'account.invoice', 'view_id': False, 'type': 'ir.actions.act_window', 'domain': [('id', 'in', [x.id for x in self.invoice_ids])], } @api.multi def button_dummy(self): return True @api.multi def cancel(self): for rec in self: for move in rec.move_line_ids.mapped('move_id'): if rec.invoice_ids: move.line_ids.remove_move_reconcile() move.button_cancel() move.unlink() rec.state = 'draft' @api.multi def unlink(self): if any(rec.state != 'draft' for rec in self): raise UserError( _("You can not delete a payment that is already posted")) return super(account_payment, self).unlink() @api.multi def post(self): """ Create the journal items for the payment and update the payment's state to 'posted'. A journal entry is created containing an item in the source liquidity account (selected journal's default_debit or default_credit) and another in the destination reconciliable account (see _compute_destination_account_id). If invoice_ids is not empty, there will be one reconciliable move line per invoice to reconcile with. If the payment is a transfer, a second journal entry is created in the destination journal to receive money from the transfer account. """ for rec in self: if rec.state != 'draft': raise UserError( _("Only a draft payment can be posted. Trying to post a payment in state %s." ) % rec.state) if any(inv.state != 'open' for inv in rec.invoice_ids): raise ValidationError( _("The payment cannot be processed because the invoice is not open!" )) # Use the right sequence to set the name if rec.payment_type == 'transfer': sequence = rec.env.ref('account.sequence_payment_transfer') else: if rec.partner_type == 'customer': if rec.payment_type == 'inbound': sequence = rec.env.ref( 'account.sequence_payment_customer_invoice') if rec.payment_type == 'outbound': sequence = rec.env.ref( 'account.sequence_payment_customer_refund') if rec.partner_type == 'supplier': if rec.payment_type == 'inbound': sequence = rec.env.ref( 'account.sequence_payment_supplier_refund') if rec.payment_type == 'outbound': sequence = rec.env.ref( 'account.sequence_payment_supplier_invoice') rec.name = sequence.with_context( ir_sequence_date=rec.payment_date).next_by_id() # Create the journal entry amount = rec.amount * (rec.payment_type in ('outbound', 'transfer') and 1 or -1) move = rec._create_payment_entry(amount) # In case of a transfer, the first journal entry created debited the source liquidity account and credited # the transfer account. Now we debit the transfer account and credit the destination liquidity account. if rec.payment_type == 'transfer': transfer_credit_aml = move.line_ids.filtered( lambda r: r.account_id == rec.company_id. transfer_account_id) transfer_debit_aml = rec._create_transfer_entry(amount) (transfer_credit_aml + transfer_debit_aml).reconcile() rec.state = 'posted' def _create_payment_entry(self, amount): """ Create a journal entry corresponding to a payment, if the payment references invoice(s) they are reconciled. Return the journal entry. """ aml_obj = self.env['account.move.line'].with_context( check_move_validity=False) debit, credit, amount_currency = aml_obj.with_context( date=self.payment_date).compute_amount_fields( amount, self.currency_id, self.company_id.currency_id) move = self.env['account.move'].create(self._get_move_vals()) #Write line corresponding to invoice payment counterpart_aml_dict = self._get_shared_move_line_vals( debit, credit, amount_currency, move.id, False) counterpart_aml_dict.update( self._get_counterpart_move_line_vals(self.invoice_ids)) counterpart_aml_dict.update({ 'currency_id': self.currency_id != self.company_id.currency_id and self.currency_id.id or False }) counterpart_aml = aml_obj.create(counterpart_aml_dict) #Reconcile with the invoices if self.payment_difference_handling == 'reconcile': self.invoice_ids.register_payment(counterpart_aml, self.writeoff_account_id, self.journal_id) else: self.invoice_ids.register_payment(counterpart_aml) #Write counterpart lines liquidity_aml_dict = self._get_shared_move_line_vals( credit, debit, -amount_currency, move.id, False) liquidity_aml_dict.update(self._get_liquidity_move_line_vals(-amount)) aml_obj.create(liquidity_aml_dict) move.post() return move def _create_transfer_entry(self, amount): """ Create the journal entry corresponding to the 'incoming money' part of an internal transfer, return the reconciliable move line """ aml_obj = self.env['account.move.line'].with_context( check_move_validity=False) debit, credit, amount_currency = aml_obj.with_context( date=self.payment_date).compute_amount_fields( amount, self.currency_id, self.company_id.currency_id) amount_currency = self.destination_journal_id.currency_id and self.currency_id.with_context( date=self.payment_date).compute( amount, self.destination_journal_id.currency_id) or 0 dst_move = self.env['account.move'].create( self._get_move_vals(self.destination_journal_id)) dst_liquidity_aml_dict = self._get_shared_move_line_vals( debit, credit, amount_currency, dst_move.id) dst_liquidity_aml_dict.update({ 'name': _('Transfer from %s') % self.journal_id.name, 'account_id': self.destination_journal_id.default_credit_account_id.id, 'currency_id': self.destination_journal_id.currency_id.id, 'payment_id': self.id, 'journal_id': self.destination_journal_id.id }) aml_obj.create(dst_liquidity_aml_dict) transfer_debit_aml_dict = self._get_shared_move_line_vals( credit, debit, 0, dst_move.id) transfer_debit_aml_dict.update({ 'name': self.name, 'payment_id': self.id, 'account_id': self.company_id.transfer_account_id.id, 'journal_id': self.destination_journal_id.id }) if self.currency_id != self.company_id.currency_id: transfer_debit_aml_dict.update({ 'currency_id': self.currency_id.id, 'amount_currency': -self.amount, }) transfer_debit_aml = aml_obj.create(transfer_debit_aml_dict) dst_move.post() return transfer_debit_aml def _get_move_vals(self, journal=None): """ Return dict to create the payment move """ journal = journal or self.journal_id if not journal.sequence_id: raise UserError( _('Configuration Error !'), _('The journal %s does not have a sequence, please specify one.' ) % journal.name) if not journal.sequence_id.active: raise UserError( _('Configuration Error !'), _('The sequence of journal %s is deactivated.') % journal.name) name = journal.with_context( ir_sequence_date=self.payment_date).sequence_id.next_by_id() return { 'name': name, 'date': self.payment_date, 'ref': self.communication or '', 'company_id': self.company_id.id, 'journal_id': journal.id, } def _get_shared_move_line_vals(self, debit, credit, amount_currency, move_id, invoice_id=False): """ Returns values common to both move lines (except for debit, credit and amount_currency which are reversed) """ return { 'partner_id': self.payment_type in ('inbound', 'outbound') and self.env['res.partner']._find_accounting_partner( self.partner_id).id or False, 'invoice_id': invoice_id and invoice_id.id or False, 'move_id': move_id, 'debit': debit, 'credit': credit, 'amount_currency': amount_currency or False, } def _get_counterpart_move_line_vals(self, invoice=False): if self.payment_type == 'transfer': name = self.name else: name = '' if self.partner_type == 'customer': if self.payment_type == 'inbound': name += _("Customer Payment") elif self.payment_type == 'outbound': name += _("Customer Refund") elif self.partner_type == 'supplier': if self.payment_type == 'inbound': name += _("Vendor Refund") elif self.payment_type == 'outbound': name += _("Vendor Payment") if invoice: name += ': ' for inv in invoice: name += inv.number + ', ' name = name[:len(name) - 2] return { 'name': name, 'account_id': self.destination_account_id.id, 'journal_id': self.journal_id.id, 'currency_id': self.currency_id != self.company_id.currency_id and self.currency_id.id or False, 'payment_id': self.id, } def _get_liquidity_move_line_vals(self, amount): name = self.name if self.payment_type == 'transfer': name = _('Transfer to %s') % self.destination_journal_id.name vals = { 'name': name, 'account_id': self.payment_type in ('outbound', 'transfer') and self.journal_id.default_debit_account_id.id or self.journal_id.default_credit_account_id.id, 'payment_id': self.id, 'journal_id': self.journal_id.id, 'currency_id': self.currency_id != self.company_id.currency_id and self.currency_id.id or False, } # If the journal has a currency specified, the journal item need to be expressed in this currency if self.journal_id.currency_id and self.currency_id != self.journal_id.currency_id: amount = self.currency_id.with_context( date=self.payment_date).compute(amount, self.journal_id.currency_id) debit, credit, amount_currency = self.env[ 'account.move.line'].with_context( date=self.payment_date).compute_amount_fields( amount, self.journal_id.currency_id, self.company_id.currency_id) vals.update({ 'amount_currency': amount_currency, 'currency_id': self.journal_id.currency_id.id, }) return vals
class LunchOrder(models.Model): """ A lunch order contains one or more lunch order line(s). It is associated to a user for a given date. When creating a lunch order, applicable lunch alerts are displayed. """ _name = 'lunch.order' _description = 'Lunch Order' _order = 'date desc' def _default_previous_order_ids(self): prev_order = self.env['lunch.order.line'].search( [('user_id', '=', self.env.uid)], limit=20, order='id desc') # If we return return prev_order.ids, we will have duplicates (identical orders). # Therefore, this following part removes duplicates based on product_id and note. return {(order.product_id, order.note): order.id for order in prev_order}.values() user_id = fields.Many2one('res.users', 'User', required=True, readonly=True, states={'new': [('readonly', False)]}, default=lambda self: self.env.uid) date = fields.Date('Date', required=True, readonly=True, states={'new': [('readonly', False)]}, default=fields.Date.context_today) order_line_ids = fields.One2many('lunch.order.line', 'order_id', 'Products', ondelete="cascade", readonly=True, copy=True, states={ 'new': [('readonly', False)], False: [('readonly', False)] }) total = fields.Float(compute='_compute_total', string="Total", store=True) state = fields.Selection([('new', 'New'), ('confirmed', 'Received'), ('cancelled', 'Cancelled')], 'Status', readonly=True, index=True, copy=False, default='new', compute='_compute_order_state', store=True) alerts = fields.Text(compute='_compute_alerts_get', string="Alerts") previous_order_ids = fields.Many2many( 'lunch.order.line', compute='_compute_previous_order_ids', default=lambda self: self._default_previous_order_ids()) company_id = fields.Many2one('res.company', related='user_id.company_id', store=True) currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True, store=True) cash_move_balance = fields.Monetary(compute='_compute_cash_move_balance', multi='cash_move_balance') balance_visible = fields.Boolean(compute='_compute_cash_move_balance', multi='cash_move_balance') @api.one @api.depends('order_line_ids') def _compute_total(self): """ get and sum the order lines' price """ self.total = sum(orderline.price for orderline in self.order_line_ids) @api.multi def name_get(self): return [(order.id, '%s %s' % (_('Lunch Order'), '#%d' % order.id)) for order in self] @api.depends('state') def _compute_alerts_get(self): """ get the alerts to display on the order form """ alert_msg = [ alert.message for alert in self.env['lunch.alert'].search([]) if alert.display ] if self.state == 'new': self.alerts = alert_msg and '\n'.join(alert_msg) or False @api.depends('user_id') def _compute_previous_order_ids(self): self.previous_order_ids = self._default_previous_order_ids() @api.one @api.depends('user_id') def _compute_cash_move_balance(self): domain = [('user_id', '=', self.user_id.id)] lunch_cash = self.env['lunch.cashmove'].read_group( domain, ['amount', 'user_id'], ['user_id']) if len(lunch_cash): self.cash_move_balance = lunch_cash[0]['amount'] self.balance_visible = (self.user_id == self.env.user) or self.user_has_groups( 'lunch.group_lunch_manager') @api.one @api.constrains('date') def _check_date(self): """ Prevents the user to create an order in the past """ date_order = datetime.datetime.strptime(self.date, '%Y-%m-%d') date_today = datetime.datetime.strptime( fields.Date.context_today(self), '%Y-%m-%d') if (date_order < date_today): raise UserError(_('The date of your order is in the past.')) @api.one @api.depends('order_line_ids.state') def _compute_order_state(self): """ Update the state of lunch.order based on its orderlines. Here is the logic: - if at least one order line is cancelled, the order is set as cancelled - if no line is cancelled but at least one line is not confirmed, the order is set as new - if all lines are confirmed, the order is set as confirmed """ if not self.order_line_ids: self.state = 'new' else: isConfirmed = True for orderline in self.order_line_ids: if orderline.state == 'cancelled': self.state = 'cancelled' return elif orderline.state == 'confirmed': continue else: isConfirmed = False if isConfirmed: self.state = 'confirmed' else: self.state = 'new' return
class AccountVoucher(models.Model): @api.one @api.depends('move_id.line_ids.reconciled', 'move_id.line_ids.account_id.internal_type') def _check_paid(self): self.paid = any([((line.account_id.internal_type, 'in', ('receivable', 'payable')) and line.reconciled) for line in self.move_id.line_ids]) @api.model def _get_currency(self): journal = self.env['account.journal'].browse( self._context.get('journal_id', False)) if journal.currency_id: return journal.currency_id.id return self.env.user.company_id.currency_id.id @api.multi @api.depends('name', 'number') def name_get(self): return [(r.id, (r.number or _('Voucher'))) for r in self] @api.one @api.depends('journal_id', 'company_id') def _get_journal_currency(self): self.currency_id = self.journal_id.currency_id.id or self.company_id.currency_id.id @api.model def _default_journal(self): voucher_type = self._context.get('voucher_type', 'sale') company_id = self._context.get('company_id', self.env.user.company_id.id) domain = [ ('type', '=', voucher_type), ('company_id', '=', company_id), ] return self.env['account.journal'].search(domain, limit=1) @api.multi @api.depends('tax_correction', 'line_ids.price_subtotal') def _compute_total(self): for voucher in self: total = 0 tax_amount = 0 for line in voucher.line_ids: tax_info = line.tax_ids.compute_all(line.price_unit, voucher.currency_id, line.quantity, line.product_id, voucher.partner_id) total += tax_info.get('total_included', 0.0) tax_amount += sum([ t.get('amount', 0.0) for t in tax_info.get('taxes', False) ]) voucher.amount = total + voucher.tax_correction voucher.tax_amount = tax_amount @api.one @api.depends('account_pay_now_id', 'account_pay_later_id', 'pay_now') def _get_account(self): self.account_id = self.account_pay_now_id if self.pay_now == 'pay_now' else self.account_pay_later_id _name = 'account.voucher' _description = 'Accounting Voucher' _inherit = ['mail.thread'] _order = "date desc, id desc" voucher_type = fields.Selection([('sale', 'Sale'), ('purchase', 'Purchase')], string='Type', readonly=True, states={'draft': [('readonly', False)]}, oldname="type") name = fields.Char('Payment Reference', readonly=True, states={'draft': [('readonly', False)]}, default='') date = fields.Date(readonly=True, select=True, states={'draft': [('readonly', False)]}, help="Effective date for accounting entries", copy=False, default=fields.Date.context_today) journal_id = fields.Many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=_default_journal) account_id = fields.Many2one( 'account.account', 'Account', required=True, readonly=True, states={'draft': [('readonly', False)]}, domain= "[('deprecated', '=', False), ('internal_type','=', (pay_now == 'pay_now' and 'liquidity' or 'receivable'))]" ) line_ids = fields.One2many('account.voucher.line', 'voucher_id', 'Voucher Lines', readonly=True, copy=True, states={'draft': [('readonly', False)]}) 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', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['res.company']. _company_default_get('account.voucher')) state = fields.Selection( [('draft', 'Draft'), ('cancel', 'Cancelled'), ('proforma', 'Pro-forma'), ('posted', 'Posted')], 'Status', readonly=True, track_visibility='onchange', copy=False, default='draft', help= " * The 'Draft' status is used when a user is encoding a new and unconfirmed Voucher.\n" " * The 'Pro-forma' status is used when the voucher does not have a voucher number.\n" " * The 'Posted' status is used when user create voucher,a voucher number is generated and voucher entries are created in account.\n" " * The 'Cancelled' status is used when user cancel voucher.") reference = fields.Char('Bill Reference', readonly=True, states={'draft': [('readonly', False)]}, help="The partner reference of this document.", copy=False) amount = fields.Monetary(string='Total', store=True, readonly=True, compute='_compute_total') tax_amount = fields.Monetary(readonly=True, store=True, compute='_compute_total') tax_correction = fields.Monetary( readonly=True, states={'draft': [('readonly', False)]}, help= 'In case we have a rounding problem in the tax, use this field to correct it' ) number = fields.Char(readonly=True, copy=False) move_id = fields.Many2one('account.move', 'Journal Entry', copy=False) partner_id = fields.Many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft': [('readonly', False)]}) paid = fields.Boolean(compute='_check_paid', help="The Voucher has been totally paid.") pay_now = fields.Selection([ ('pay_now', 'Pay Directly'), ('pay_later', 'Pay Later'), ], 'Payment', select=True, readonly=True, states={'draft': [('readonly', False)]}, default='pay_later') date_due = fields.Date('Due Date', readonly=True, select=True, states={'draft': [('readonly', False)]}) @api.onchange('partner_id', 'pay_now') def onchange_partner_id(self): if self.pay_now == 'pay_now': liq_journal = self.env['account.journal'].search( [('type', 'in', ('bank', 'cash'))], limit=1) self.account_id = liq_journal.default_debit_account_id \ if self.voucher_type == 'sale' else liq_journal.default_credit_account_id else: if self.partner_id: self.account_id = self.partner_id.property_account_receivable_id \ if self.voucher_type == 'sale' else self.partner_id.property_account_payable_id else: self.account_id = self.journal_id.default_debit_account_id \ if self.voucher_type == 'sale' else self.journal_id.default_credit_account_id @api.multi def button_proforma_voucher(self): self.signal_workflow('proforma_voucher') return {'type': 'ir.actions.act_window_close'} @api.multi def proforma_voucher(self): self.action_move_line_create() @api.multi def action_cancel_draft(self): self.create_workflow() self.write({'state': 'draft'}) @api.multi def cancel_voucher(self): for voucher in self: voucher.move_id.button_cancel() voucher.move_id.unlink() self.write({'state': 'cancel', 'move_id': False}) @api.multi def unlink(self): for voucher in self: if voucher.state not in ('draft', 'cancel'): raise Warning( _('Cannot delete voucher(s) which are already opened or paid.' )) return super(AccountVoucher, self).unlink() @api.multi def first_move_line_get(self, move_id, company_currency, current_currency): debit = credit = 0.0 if self.voucher_type == 'purchase': credit = self._convert_amount(self.amount) elif self.voucher_type == 'sale': debit = self._convert_amount(self.amount) if debit < 0.0: debit = 0.0 if credit < 0.0: credit = 0.0 sign = debit - credit < 0 and -1 or 1 #set the first line of the voucher move_line = { 'name': self.name or '/', 'debit': debit, 'credit': credit, 'account_id': self.account_id.id, 'move_id': move_id, 'journal_id': self.journal_id.id, 'partner_id': self.partner_id.id, 'currency_id': company_currency != current_currency and current_currency or False, 'amount_currency': ( sign * abs(self.amount) # amount < 0 for refunds if company_currency != current_currency else 0.0), 'date': self.date, 'date_maturity': self.date_due } return move_line @api.multi def account_move_get(self): if self.number: name = self.number elif self.journal_id.sequence_id: if not self.journal_id.sequence_id.active: raise UserError( _('Please activate the sequence of selected journal !')) 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.date, 'ref': self.reference, } return move @api.multi def _convert_amount(self, amount): ''' This function convert the amount given in company currency. It takes either the rate in the voucher (if the payment_rate_currency_id is relevant) either the rate encoded in the system. :param amount: float. The amount to convert :param voucher: id of the voucher on which we want the conversion :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date field in order to select the good rate to use. :return: the amount in the currency of the voucher's company :rtype: float ''' for voucher in self: return voucher.currency_id.compute(amount, voucher.company_id.currency_id) @api.multi def voucher_move_line_create(self, line_total, move_id, company_currency, current_currency): ''' Create one account move line, on the given account move, per voucher line where amount is not 0.0. It returns Tuple with tot_line what is total of difference between debit and credit and a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists). :param voucher_id: Voucher id what we are working with :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines. :param move_id: Account move wher those lines will be joined. :param company_currency: id of currency of the company to which the voucher belong :param current_currency: id of currency of the voucher :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method) :rtype: tuple(float, list of int) ''' for line in self.line_ids: #create one move line per voucher line where amount is not 0.0 if not line.price_subtotal: continue # convert the amount set on the voucher line into the currency of the voucher's company # this calls res_curreny.compute() with the right context, so that it will take either the rate on the voucher if it is relevant or will use the default behaviour amount = self._convert_amount(line.price_unit * line.quantity) move_line = { 'journal_id': self.journal_id.id, 'name': line.name or '/', 'account_id': line.account_id.id, 'move_id': move_id, 'partner_id': self.partner_id.id, 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False, 'quantity': 1, 'credit': abs(amount) if self.voucher_type == 'sale' else 0.0, 'debit': abs(amount) if self.voucher_type == 'purchase' else 0.0, 'date': self.date, 'tax_ids': [(4, t.id) for t in line.tax_ids], 'amount_currency': line.price_subtotal if current_currency != company_currency else 0.0, } self.env['account.move.line'].create(move_line) return line_total @api.multi def action_move_line_create(self): ''' Confirm the vouchers given in ids and create the journal entries for each of them ''' for voucher in self: local_context = dict( self._context, force_company=voucher.journal_id.company_id.id) if voucher.move_id: continue company_currency = voucher.journal_id.company_id.currency_id.id current_currency = voucher.currency_id.id or company_currency # we select the context to use accordingly if it's a multicurrency case or not # But for the operations made by _convert_amount, we always need to give the date in the context ctx = local_context.copy() ctx['date'] = voucher.date ctx['check_move_validity'] = False # Create the account move record. move = self.env['account.move'].create(voucher.account_move_get()) # Get the name of the account_move just created # Create the first line of the voucher move_line = self.env['account.move.line'].with_context(ctx).create( voucher.first_move_line_get(move.id, company_currency, current_currency)) line_total = move_line.debit - move_line.credit if voucher.voucher_type == 'sale': line_total = line_total - voucher._convert_amount( voucher.tax_amount) elif voucher.voucher_type == 'purchase': line_total = line_total + voucher._convert_amount( voucher.tax_amount) # Create one move line per voucher line where amount is not 0.0 line_total = voucher.with_context(ctx).voucher_move_line_create( line_total, move.id, company_currency, current_currency) # Add tax correction to move line if any tax correction specified if voucher.tax_correction != 0.0: tax_move_line = self.env['account.move.line'].search( [('move_id', '=', move.id), ('tax_line_id', '!=', False)], limit=1) if len(tax_move_line): tax_move_line.write({ 'debit': tax_move_line.debit + voucher.tax_correction if tax_move_line.debit > 0 else 0, 'credit': tax_move_line.credit + voucher.tax_correction if tax_move_line.credit > 0 else 0 }) # We post the voucher. voucher.write({ 'move_id': move.id, 'state': 'posted', 'number': move.name }) move.post() return True @api.multi def _track_subtype(self, init_values): if 'state' in init_values: return 'account_voucher.mt_voucher_state_change' return super(AccountVoucher, self)._track_subtype(init_values)
class account_voucher_line(models.Model): _name = 'account.voucher.line' _description = 'Voucher Lines' @api.one @api.depends('price_unit', 'tax_ids', 'quantity', 'product_id', 'voucher_id.currency_id') def _compute_subtotal(self): self.price_subtotal = self.quantity * self.price_unit if self.tax_ids: taxes = self.tax_ids.compute_all( self.price_unit, self.voucher_id.currency_id, self.quantity, product=self.product_id, partner=self.voucher_id.partner_id) self.price_subtotal = taxes['total_excluded'] name = fields.Text(string='Description', required=True) sequence = fields.Integer( default=10, help="Gives the sequence of this line when displaying the voucher.") voucher_id = fields.Many2one('account.voucher', 'Voucher', required=1, ondelete='cascade') product_id = fields.Many2one('product.product', string='Product', ondelete='set null', index=True) account_id = fields.Many2one( 'account.account', string='Account', required=True, domain=[('deprecated', '=', False)], help="The income or expense account related to the selected product.") price_unit = fields.Monetary(string='Unit Price', required=True, oldname='amount') price_subtotal = fields.Monetary(string='Amount', store=True, readonly=True, compute='_compute_subtotal') quantity = fields.Float(digits=dp.get_precision('Product Unit of Measure'), required=True, default=1) account_analytic_id = fields.Many2one('account.analytic.account', 'Analytic Account') 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') def _get_account(self, product, fpos, type): accounts = product.product_tmpl_id.get_product_accounts(fpos) if type == 'sale': return accounts['income'] return accounts['expense'] @api.multi def product_id_change(self, product_id, partner_id=False, price_unit=False, company_id=None, currency_id=None, type=None): context = self._context company_id = company_id if company_id is not None else context.get( 'company_id', False) company = self.env['res.company'].browse(company_id) currency = self.env['res.currency'].browse(currency_id) if not partner_id: raise UserError(_("You must first select a partner!")) part = self.env['res.partner'].browse(partner_id) if part.lang: self = self.with_context(lang=part.lang) product = self.env['product.product'].browse(product_id) fpos = part.property_account_position_id.id account = self._get_account(product, fpos, type) values = { 'name': product.partner_ref, 'account_id': account.id, } if type == 'purchase': values['price_unit'] = price_unit or product.standard_price taxes = product.supplier_taxes_id or account.tax_ids if product.description_purchase: values['name'] += '\n' + product.description_purchase else: values['price_unit'] = product.lst_price taxes = product.taxes_id or account.tax_ids if product.description_sale: values['name'] += '\n' + product.description_sale values['tax_ids'] = taxes.ids if company and currency: if company.currency_id != currency: if type == 'purchase': values['price_unit'] = product.standard_price values['price_unit'] = values['price_unit'] * currency.rate return {'value': values, 'domain': {}}