Esempio n. 1
0
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
Esempio n. 2
0
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")
Esempio n. 3
0
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)
Esempio n. 4
0
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']
Esempio n. 5
0
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()
Esempio n. 6
0
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)
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
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)
Esempio n. 10
0
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': {}}