class AccountCheck(models.Model):

    _name = 'account.check'
    _description = 'Account Check'
    _order = "id desc"
    _inherit = ['mail.thread']

    operation_ids = fields.One2many(
        'account.check.operation',
        'check_id',
    )
    name = fields.Char(
        required=True,
        readonly=True,
        copy=False,
        states={'draft': [('readonly', False)]},
    )
    number = fields.Integer(required=True,
                            readonly=True,
                            states={'draft': [('readonly', False)]},
                            copy=False)
    checkbook_id = fields.Many2one(
        'account.checkbook',
        'Checkbook',
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    issue_check_subtype = fields.Selection(
        related='checkbook_id.issue_check_subtype')
    type = fields.Selection(
        [('issue_check', 'Issue Check'), ('third_check', 'Third Check')],
        readonly=True,
    )
    partner_id = fields.Many2one(related='operation_ids.partner_id',
                                 readonly=True,
                                 store=True)
    state = fields.Selection(
        [
            ('draft', 'Draft'),
            ('holding', 'Holding'),
            ('deposited', 'Deposited'),
            ('selled', 'Selled'),
            ('delivered', 'Delivered'),
            ('transfered', 'Transfered'),
            ('reclaimed', 'Reclaimed'),
            ('withdrawed', 'Withdrawed'),
            ('handed', 'Handed'),
            ('rejected', 'Rejected'),
            ('debited', 'Debited'),
            ('returned', 'Returned'),
            ('changed', 'Changed'),
            ('cancel', 'Cancel'),
        ],
        required=True,
        default='draft',
        copy=False,
        compute='_compute_state',
        store=True,
    )
    issue_date = fields.Date(
        'Issue Date',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
        default=fields.Date.context_today,
    )
    owner_vat = fields.Char('Owner Vat',
                            readonly=True,
                            states={'draft': [('readonly', False)]})
    owner_name = fields.Char('Owner Name',
                             readonly=True,
                             states={'draft': [('readonly', False)]})
    bank_id = fields.Many2one('res.bank',
                              'Bank',
                              readonly=True,
                              states={'draft': [('readonly', False)]})

    amount = fields.Monetary(currency_field='company_currency_id',
                             readonly=True,
                             states={'draft': [('readonly', False)]})
    amount_currency = fields.Monetary(
        currency_field='currency_id',
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    currency_id = fields.Many2one(
        'res.currency',
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    payment_date = fields.Date(readonly=True,
                               states={'draft': [('readonly', False)]})
    journal_id = fields.Many2one('account.journal',
                                 string='Journal',
                                 required=True,
                                 domain=[('type', 'in', ['cash', 'bank'])],
                                 readonly=True,
                                 states={'draft': [('readonly', False)]})
    company_id = fields.Many2one(
        related='journal_id.company_id',
        readonly=True,
        store=True,
    )
    company_currency_id = fields.Many2one(
        related='company_id.currency_id',
        readonly=True,
    )

    @api.multi
    @api.constrains('issue_date', 'payment_date')
    @api.onchange('issue_date', 'payment_date')
    def onchange_date(self):
        for rec in self:
            if (rec.issue_date and rec.payment_date
                    and rec.issue_date > rec.payment_date):
                raise UserError(
                    _('Check Payment Date must be greater than Issue Date'))

    @api.multi
    @api.constrains(
        'type',
        'number',
    )
    def issue_number_interval(self):
        for rec in self:
            # if not range, then we dont check it
            if rec.type == 'issue_check' and rec.checkbook_id.range_to:
                if rec.number > rec.checkbook_id.range_to:
                    raise UserError(
                        _("Check number (%s) can't be greater than %s on "
                          "checkbook %s (%s)") % (
                              rec.number,
                              rec.checkbook_id.range_to,
                              rec.checkbook_id.name,
                              rec.checkbook_id.id,
                          ))
                elif rec.number == rec.checkbook_id.range_to:
                    rec.checkbook_id.state = 'used'
        return False

    @api.multi
    @api.constrains(
        'type',
        'owner_name',
        'bank_id',
    )
    def _check_unique(self):
        for rec in self:
            if rec.type == 'issue_check':
                same_checks = self.search([
                    ('checkbook_id', '=', rec.checkbook_id.id),
                    ('type', '=', rec.type),
                    ('number', '=', rec.number),
                ])
                same_checks -= self
                if same_checks:
                    raise ValidationError(
                        _('Check Number (%s) must be unique per Checkbook!\n'
                          '* Check ids: %s') % (rec.name, same_checks.ids))
            elif self.type == 'third_check':
                # agregamos condicion de company ya que un cheque de terceros
                # se puede pasar entre distintas cias
                same_checks = self.search([
                    ('company_id', '=', rec.company_id.id),
                    ('bank_id', '=', rec.bank_id.id),
                    ('owner_name', '=', rec.owner_name),
                    ('type', '=', rec.type),
                    ('number', '=', rec.number),
                ])
                same_checks -= self
                if same_checks:
                    raise ValidationError(
                        _('Check Number (%s) must be unique per Owner and Bank!'
                          '\n* Check ids: %s') % (rec.name, same_checks.ids))
        return True

    @api.multi
    def _del_operation(self, origin):
        """
        We check that the operation that is being cancel is the last operation
        done (same as check state)
        """
        for rec in self:
            if not rec.operation_ids or rec.operation_ids[0].origin != origin:
                raise ValidationError(
                    _('You can not cancel this operation because this is not '
                      'the last operation over the check.\nCheck (id): %s (%s)'
                      ) % (rec.name, rec.id))
            rec.operation_ids[0].origin = False
            rec.operation_ids[0].unlink()

    @api.multi
    def _add_operation(self, operation, origin, partner=None, date=False):
        for rec in self:
            rec._check_state_change(operation)
            # agregamos validacion de fechas
            date = date or fields.Datetime.now()
            if rec.operation_ids and rec.operation_ids[0].date > date:
                raise ValidationError(
                    _('The date of a new check operation can not be minor than '
                      'last operation date.\n'
                      '* Check Id: %s\n'
                      '* Check Number: %s\n'
                      '* Operation: %s\n'
                      '* Operation Date: %s\n'
                      '* Last Operation Date: %s') %
                    (rec.id, rec.name, operation, date,
                     rec.operation_ids[0].date))
            vals = {
                'operation': operation,
                'date': date,
                'check_id': rec.id,
                'origin': '%s,%i' % (origin._name, origin.id),
                'partner_id': partner and partner.id or False,
            }
            rec.operation_ids.create(vals)

    @api.multi
    @api.depends(
        'operation_ids.operation',
        'operation_ids.date',
    )
    def _compute_state(self):
        for rec in self:
            if rec.operation_ids:
                operation = rec.operation_ids[0].operation
                rec.state = operation
            else:
                rec.state = 'draft'

    @api.multi
    def _check_state_change(self, operation):
        """
        We only check state change from _add_operation because we want to
        leave the user the possibility of making anything from interface.
        Necesitamos este chequeo para evitar, por ejemplo, que un cheque se
        agregue dos veces en un pago y luego al confirmar se entregue dos veces
        On operation_from_state_map dictionary:
        * key is 'to state'
        * value is 'from states'
        """
        self.ensure_one()
        # if we do it from _add_operation only, not from a contraint of before
        # computing the value, we can just read it
        old_state = self.state
        operation_from_state_map = {
            # 'draft': [False],
            'holding':
            ['draft', 'deposited', 'selled', 'delivered', 'transfered'],
            'delivered': ['holding'],
            'deposited': ['holding', 'rejected'],
            'selled': ['holding'],
            'handed': ['draft'],
            'transfered': ['holding'],
            'withdrawed': ['draft'],
            'rejected': ['delivered', 'deposited', 'selled', 'handed'],
            'debited': ['handed'],
            'returned': ['handed', 'holding'],
            'changed': ['handed', 'holding'],
            'cancel': ['draft'],
            'reclaimed': ['rejected'],
        }
        from_states = operation_from_state_map.get(operation)
        if not from_states:
            raise ValidationError(
                _('Operation %s not implemented for checks!') % operation)
        if old_state not in from_states:
            raise ValidationError(
                _('You can not "%s" a check from state "%s"!\n'
                  'Check nbr (id): %s (%s)') %
                (self.operation_ids._fields['operation'].convert_to_export(
                    operation,
                    self.env), self._fields['state'].convert_to_export(
                        old_state, self.env), self.name, self.id))

    @api.multi
    def unlink(self):
        for rec in self:
            if rec.state not in ('draft', 'cancel'):
                raise ValidationError(
                    _('The Check must be in draft state for unlink !'))
        return super(AccountCheck, self).unlink()

# checks operations from checks

    @api.multi
    def bank_debit(self):
        self.ensure_one()
        if self.state in ['handed']:
            vals = self.get_bank_vals('bank_debit', self.journal_id)
            action_date = self._context.get('action_date')
            vals['date'] = action_date
            move = self.env['account.move'].create(vals)
            self.handed_reconcile(move)
            move.post()
            self._add_operation('debited', move, date=action_date)

    @api.multi
    def handed_reconcile(self, move):
        """
        Funcion que por ahora solo intenta conciliar cheques propios entregados
        cuando se hace un debito o cuando el proveedor lo rechaza
        """

        self.ensure_one()
        debit_account = self.company_id._get_check_account('deferred')

        # conciliamos
        if debit_account.reconcile:
            operation = self._get_operation('handed')
            if operation.origin._name == 'account.payment':
                move_lines = operation.origin.move_line_ids
            elif operation.origin._name == 'account.move':
                move_lines = operation.origin.line_ids
            move_lines |= move.line_ids
            move_lines = move_lines.filtered(
                lambda x: x.account_id == debit_account)
            if len(move_lines) != 2:
                raise ValidationError(
                    _('We have found more or less thant two journal items to '
                      'reconcile with check debit.\n'
                      '*Journal items: %s') % move_lines.ids)
            move_lines.reconcile()

    @api.model
    def get_third_check_account(self):
        """
        For third checks, if we use a journal only for third checks, we use
        accounts on journal, if not we use company account
        # TODO la idea es depreciar esto y que si se usa cheques de terceros
        se use la misma cuenta que la del diario y no la cuenta configurada en
        la cia, lo dejamos por ahora por nosotros y 4 clientes que estan asi
        """
        # self.ensure_one()
        # desde los pagos, pueden venir mas de un cheque pero para que
        # funcione bien, todos los cheques deberian usar la misma cuenta,
        # hacemos esa verificación
        account = self.env['account.account']
        for rec in self:
            credit_account = rec.journal_id.default_credit_account_id
            debit_account = rec.journal_id.default_debit_account_id
            inbound_methods = rec.journal_id['inbound_payment_method_ids']
            outbound_methods = rec.journal_id['outbound_payment_method_ids']
            # si hay cuenta en diario y son iguales, y si los metodos de pago
            # y cobro son solamente uno, usamos el del diario, si no, usamos el
            # de la compañía
            if credit_account and credit_account == debit_account and len(
                    inbound_methods) == 1 and len(outbound_methods) == 1:
                account |= credit_account
            else:
                account |= rec.company_id._get_check_account('holding')
        if len(account) != 1:
            raise ValidationError(_('Error not specified'))
        return account

    @api.model
    def _get_checks_to_date_on_state(self, state, date, force_domain=None):
        """
        Devuelve el listado de cheques que a la fecha definida se encontraban
        en el estadao definido.
        Esta función no la usamos en este módulo pero si en otros que lo
        extienden
        La funcion devuelve un listado de las operaciones a traves de las
        cuales se puede acceder al cheque, devolvemos las operaciones porque
        dan información util de fecha, partner y demas
        """
        # buscamos operaciones anteriores a la fecha que definan este estado
        if not force_domain:
            force_domain = []
        operations = self.operation_ids.search(
            [('date', '<=', date), ('operation', '=', state)] + force_domain)

        for operation in operations:
            # buscamos si hay alguna otra operacion posterior para el cheque
            newer_op = operation.search([
                ('date', '<=', date),
                ('id', '>', operation.id),
                ('check_id', '=', operation.check_id.id),
            ])
            # si hay una operacion posterior borramos la op del cheque porque
            # hubo otra operación antes de la fecha
            if newer_op:
                operations -= operation
        return operations

    @api.multi
    def _get_operation(self, operation, partner_required=False):
        self.ensure_one()
        op = self.operation_ids.search([('check_id', '=', self.id),
                                        ('operation', '=', operation)],
                                       limit=1)
        if partner_required:
            if not op.partner_id:
                raise ValidationError(
                    _('The %s (id %s) operation has no partner linked.'
                      'You will need to do it manually.') % (operation, op.id))
        return op

    @api.multi
    def claim(self):
        self.ensure_one()
        if self.state in ['rejected'] and self.type == 'third_check':
            # anulamos la operación en la que lo recibimos
            operation = self._get_operation('holding', True)
            return self.action_create_debit_note(
                'reclaimed', 'customer', operation.partner_id,
                self.company_id._get_check_account('rejected'))

    @api.multi
    def customer_return(self):
        self.ensure_one()
        if self.state in ['holding'] and self.type == 'third_check':
            operation = self._get_operation('holding', True)
            return self.action_create_debit_note(
                'returned', 'customer', operation.partner_id,
                self.get_third_check_account())

    @api.multi
    def reject(self):
        self.ensure_one()
        if self.state in ['deposited', 'selled']:
            operation = self._get_operation(self.state)
            if operation.origin._name == 'account.payment':
                journal = operation.origin.destination_journal_id
            # for compatibility with migration from v8
            elif operation.origin._name == 'account.move':
                journal = operation.origin.journal_id
            else:
                raise ValidationError(
                    _('The deposit operation is not linked to a payment.'
                      'If you want to reject you need to do it manually.'))
            vals = self.get_bank_vals('bank_reject', journal)
            action_date = self._context.get('action_date')
            vals['date'] = action_date
            move = self.env['account.move'].create(vals)
            move.post()
            self._add_operation('rejected', move, date=action_date)
        elif self.state == 'delivered':
            operation = self._get_operation(self.state, True)
            return self.action_create_debit_note(
                'rejected', 'supplier', operation.partner_id,
                self.company_id._get_check_account('rejected'))
        elif self.state == 'handed':
            operation = self._get_operation(self.state, True)
            return self.action_create_debit_note(
                'rejected', 'supplier', operation.partner_id,
                self.company_id._get_check_account('deferred'))

    @api.multi
    def action_create_debit_note(self, operation, partner_type, partner,
                                 account):
        self.ensure_one()
        action_date = self._context.get('action_date')

        if partner_type == 'supplier':
            invoice_type = 'in_invoice'
            journal_type = 'purchase'
            view_id = self.env.ref('account.invoice_supplier_form').id
        else:
            invoice_type = 'out_invoice'
            journal_type = 'sale'
            view_id = self.env.ref('account.invoice_form').id

        journal = self.env['account.journal'].search([
            ('company_id', '=', self.company_id.id),
            ('type', '=', journal_type),
        ],
                                                     limit=1)

        # si pedimos rejected o reclamo, devolvemos mensaje de rechazo y cuenta
        # de rechazo
        if operation in ['rejected', 'reclaimed']:
            name = 'Rechazo cheque "%s"' % (self.name)
        # si pedimos la de holding es una devolucion
        elif operation == 'returned':
            name = 'Devolución cheque "%s"' % (self.name)
        else:
            raise ValidationError(
                _('Debit note for operation %s not implemented!' %
                  (operation)))

        inv_line_vals = {
            # 'product_id': self.product_id.id,
            'name':
            name,
            'account_id':
            account.id,
            'price_unit': (self.amount_currency and self.amount_currency
                           or self.amount),
            # 'invoice_id': invoice.id,
        }

        inv_vals = {
            # this is the reference that goes on account.move.line of debt line
            # 'name': name,
            # this is the reference that goes on account.move
            'rejected_check_id': self.id,
            'reference': name,
            'date_invoice': action_date,
            'origin': _('Check nbr (id): %s (%s)') % (self.name, self.id),
            'journal_id': journal.id,
            # this is done on muticompany fix
            # 'company_id': journal.company_id.id,
            'partner_id': partner.id,
            'type': invoice_type,
            'invoice_line_ids': [(0, 0, inv_line_vals)],
        }
        if self.currency_id:
            inv_vals['currency_id'] = self.currency_id.id
        # we send internal_type for compatibility with account_document
        invoice = self.env['account.invoice'].with_context(
            internal_type='debit_note').create(inv_vals)
        self._add_operation(operation, invoice, partner, date=action_date)

        return {
            'name': name,
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'account.invoice',
            'view_id': view_id,
            'res_id': invoice.id,
            'type': 'ir.actions.act_window',
        }

    @api.multi
    def get_bank_vals(self, action, journal):
        self.ensure_one()
        # TODO improove how we get vals, get them in other functions
        if action == 'bank_debit':
            # self.journal_id.default_debit_account_id.id, al debitar
            # tenemos que usar esa misma
            credit_account = journal.default_debit_account_id
            # la contrapartida es la cuenta que reemplazamos en el pago
            debit_account = self.company_id._get_check_account('deferred')
            name = _('Check "%s" debit') % (self.name)
        elif action == 'bank_reject':
            # al transferir a un banco se usa esta. al volver tiene que volver
            # por la opuesta
            # self.destination_journal_id.default_credit_account_id
            credit_account = journal.default_debit_account_id
            debit_account = self.company_id._get_check_account('rejected')
            name = _('Check "%s" rejection') % (self.name)
        else:
            raise ValidationError(
                _('Action %s not implemented for checks!') % action)

        debit_line_vals = {
            'name': name,
            'account_id': debit_account.id,
            # 'partner_id': partner,
            'debit': self.amount,
            'amount_currency': self.amount_currency,
            'currency_id': self.currency_id.id,
            # 'ref': ref,
        }
        credit_line_vals = {
            'name': name,
            'account_id': credit_account.id,
            # 'partner_id': partner,
            'credit': self.amount,
            'amount_currency': self.amount_currency,
            'currency_id': self.currency_id.id,
            # 'ref': ref,
        }
        return {
            'ref':
            name,
            'journal_id':
            journal.id,
            'date':
            fields.Date.today(),
            'line_ids': [(0, False, debit_line_vals),
                         (0, False, credit_line_vals)],
        }
class PurchaseOrder(models.Model):

    _inherit = 'purchase.order'
    _description = 'Purchase Order'

    @api.depends('order_line.price_total')
    def _amount_all(self):
        super(PurchaseOrder, self)._amount_all()
        for order in self:
            amount_discount = 0.0
            for line in order.order_line:
                amount_discount += ((line.discount / 100) *
                                    line.price_subtotal)
            amount_total = order.amount_untaxed + order.amount_tax - \
                amount_discount
            order.update({
                'amount_discount':
                order.currency_id.round(amount_discount),
                'amount_total':
                order.currency_id.round(amount_total),
            })

    amount_untaxed = fields.Monetary(string='Subtotal')
    amount_discount = fields.Monetary(compute='_amount_all',
                                      string='Discount',
                                      store=True)

    @api.cr_uid_id_context
    def action_invoice_create(self, cr, uid, ids, context=None):
        """
        You have to make a super original method and
        update invoice lines with the discount that lines has
        on the purchase order. You can not directly update the discount
        because the discount is calculated on the invoice
        """

        account_invoice_line_obj = self.pool.get('account.invoice.line')

        res = super(PurchaseOrder, self).action_invoice_create(cr,
                                                               uid,
                                                               ids,
                                                               context=context)
        invoice_lines_ids = account_invoice_line_obj.search(
            cr, uid, [('invoice_id', '=', res)], context=context)
        invoice_lines = account_invoice_line_obj.browse(cr,
                                                        uid,
                                                        invoice_lines_ids,
                                                        context=context)

        for purchase in self.browse(cr, uid, ids, context=context):
            """zip is a function that enables iterating through two
               lists simultaneously.
               for a,b in (list_a,list_b), where a is iterator for list_a
               and b is iterator for list_b
               zip know if any of list is empty and stop the iteration"""
            """In this for, iterates in both list and extract the discount
               for purchase line
               and update the invoice line with the id (invoice_line.id)"""
            for purchase_line, invoice_line in zip(purchase.order_line,
                                                   invoice_lines):
                if purchase_line.discount is not None:
                    discount = purchase_line.discount
                    account_invoice_line_obj.write(cr,
                                                   uid, [invoice_line.id],
                                                   {'discount': discount},
                                                   context=context)
        return res
Beispiel #3
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),
             ('product_id.active', '!=', False)],
            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
Beispiel #4
0
class AccountPayment(models.Model):

    _inherit = 'account.payment'
    # _name = 'account.check'
    # _description = 'Account Check'
    # _order = "id desc"
    # _inherit = ['mail.thread']

    # communication = fields.Char(
    #     # because onchange function is not called on onchange and we want
    #     # to clean check number name
    #     copy=False,
    # )
    # TODO tal vez renombrar a check_ids
    check_ids = fields.Many2many(
        'account.check',
        # 'account.move.line',
        # 'check_deposit_id',
        string='Checks',
        copy=False,
        readonly=True,
        states={'draft': [('readonly', '=', False)]}
    )
    # only for v8 comatibility where more than one check could be received
    # or issued
    check_ids_copy = fields.Many2many(
        related='check_ids',
        readonly=True,
    )
    readonly_currency_id = fields.Many2one(
        related='currency_id',
        readonly=True,
    )
    readonly_amount = fields.Monetary(
        # string='Payment Amount',
        # required=True
        related='amount',
        readonly=True,
    )
    # we add this field for better usability on issue checks and received
    # checks. We keep m2m field for backward compatibility where we allow to
    # use more than one check per payment
    check_id = fields.Many2one(
        'account.check',
        compute='_compute_check',
        string='Check',
        # string='Payment Amount',
        # required=True
    )

    @api.multi
    @api.depends('check_ids')
    def _compute_check(self):
        for rec in self:
            # we only show checks for issue checks or received thid checks
            # if len of checks is 1
            if rec.payment_method_code in (
                    'received_third_check',
                    'issue_check',) and len(rec.check_ids) == 1:
                rec.check_id = rec.check_ids[0].id

# check fields, just to make it easy to load checks without need to create
# them by a m2o record
    check_name = fields.Char(
        'Check Name',
        # required=True,
        readonly=True,
        copy=False,
        states={'draft': [('readonly', False)]},
    )
    check_number = fields.Integer(
        'Check Number',
        # required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
        copy=False
    )
    check_issue_date = fields.Date(
        'Check Issue Date',
        # required=True,
        readonly=True,
        copy=False,
        states={'draft': [('readonly', False)]},
        default=fields.Date.context_today,
    )
    check_payment_date = fields.Date(
        'Check Payment Date',
        readonly=True,
        help="Only if this check is post dated",
        states={'draft': [('readonly', False)]}
    )
    checkbook_id = fields.Many2one(
        'account.checkbook',
        'Checkbook',
        readonly=True,
        states={'draft': [('readonly', False)]},
        # TODO hacer con un onchange
        # default=_get_checkbook,
    )
    check_subtype = fields.Selection(
        related='checkbook_id.issue_check_subtype',
    )
    check_bank_id = fields.Many2one(
        'res.bank',
        'Check Bank',
        readonly=True,
        copy=False,
        states={'draft': [('readonly', False)]}
    )
    check_owner_vat = fields.Char(
        # TODO rename to Owner VAT
        'Check Owner Vat',
        readonly=True,
        copy=False,
        states={'draft': [('readonly', False)]}
    )
    check_owner_name = fields.Char(
        'Check Owner Name',
        readonly=True,
        copy=False,
        states={'draft': [('readonly', False)]}
    )
    check_type = fields.Char(
        compute='_compute_check_type',
        # this fields is to help with code and view
    )

    @api.multi
    @api.depends('payment_method_code')
    def _compute_check_type(self):
        for rec in self:
            if rec.payment_method_code == 'issue_check':
                rec.check_type = 'issue_check'
            elif rec.payment_method_code in [
                    'received_third_check',
                    'delivered_third_check']:
                rec.check_type = 'third_check'


# on change methods

    # @api.constrains('check_ids')
    @api.onchange('check_ids', 'payment_method_code')
    def onchange_checks(self):
        # we only overwrite if payment method is delivered
        if self.payment_method_code == 'delivered_third_check':
            self.amount = sum(self.check_ids.mapped('amount'))

    # TODo activar
    @api.one
    @api.onchange('check_number', 'checkbook_id')
    def change_check_number(self):
        # TODO make default padding a parameter
        if self.payment_method_code in ['received_third_check']:
            if not self.check_number:
                check_name = False
            else:
                # TODO make optional
                padding = 8
                if len(str(self.check_number)) > padding:
                    padding = len(str(self.check_number))
                # communication = _('Check nbr %s') % (
                check_name = ('%%0%sd' % padding % self.check_number)
                # communication = (
                #     '%%0%sd' % padding % self.check_number)
            self.check_name = check_name

    @api.onchange('check_issue_date', 'check_payment_date')
    def onchange_date(self):
        if (
                self.check_issue_date and self.check_payment_date and
                self.check_issue_date > self.check_payment_date):
            self.check_payment_date = False
            raise UserError(
                _('Check Payment Date must be greater than Issue Date'))

    @api.one
    @api.onchange('partner_id')
    def onchange_partner_check(self):
        commercial_partner = self.partner_id.commercial_partner_id
        self.check_bank_id = (
            commercial_partner.bank_ids and
            commercial_partner.bank_ids[0].bank_id.id or False)
        self.check_owner_name = commercial_partner.name
        # TODO use document number instead of vat?
        self.check_owner_vat = commercial_partner.vat

    @api.onchange('payment_method_code')
    def _onchange_payment_method_code(self):
        if self.payment_method_code == 'issue_check':
            checkbook = self.env['account.checkbook'].search([
                ('state', '=', 'active'),
                ('journal_id', '=', self.journal_id.id)],
                limit=1)
            self.checkbook_id = checkbook

    @api.onchange('checkbook_id')
    def onchange_checkbook(self):
        if self.checkbook_id:
            self.check_number = self.checkbook_id.next_number


# post methods
    @api.model
    def create(self, vals):
        issue_checks = self.env.ref(
            'account_check.account_payment_method_issue_check')
        if vals['payment_method_id'] == issue_checks.id and vals.get(
                'checkbook_id'):
            checkbook = self.env['account.checkbook'].browse(
                vals['checkbook_id'])
            vals.update({
                # beacause number was readonly we write it here
                'check_number': checkbook.next_number,
                'check_name': checkbook.sequence_id.next_by_id(),
            })
        return super(AccountPayment, self.sudo()).create(vals)

    @api.multi
    def cancel(self):
        res = super(AccountPayment, self).cancel()
        for rec in self:
            rec.do_checks_operations(cancel=True)
            # if rec.check_id:
            #     # rec.check_id._add_operation('cancel')
            #     rec.check_id._del_operation()
            #     rec.check_id.unlink()
            # elif rec.check_ids:
            #     rec.check_ids._del_operation()
        return res

    @api.multi
    def create_check(self, check_type, operation, bank):
        self.ensure_one()

        check_vals = {
            'bank_id': bank.id,
            'owner_name': self.check_owner_name,
            'owner_vat': self.check_owner_vat,
            'number': self.check_number,
            'name': self.check_name,
            'checkbook_id': self.checkbook_id.id,
            'issue_date': self.check_issue_date,
            'type': self.check_type,
            'journal_id': self.journal_id.id,
            'amount': self.amount,
            'payment_date': self.check_payment_date,
            # TODO arreglar que monto va de amount y cual de amount currency
            # 'amount_currency': self.amount,
            'currency_id': self.currency_id.id,
        }
        check = self.env['account.check'].create(check_vals)
        self.check_ids = [(4, check.id, False)]
        check._add_operation(operation, self, self.partner_id)
        return check

    @api.multi
    def get_third_check_account(self):
        """
        For third checks, if we use a journal only for third checks, we use
        accounts on journal, if not we use company account
        """
        self.ensure_one()
        if self.payment_type in ('outbound', 'transfer'):
            account = self.journal_id.default_debit_account_id
            methods_field = 'outbound_payment_method_ids'
        else:
            account = self.journal_id.default_credit_account_id
            methods_field = 'inbound_payment_method_ids'
        if len(self.journal_id[methods_field]) > 1 or not account:
            account = self.company_id._get_check_account('holding')
        return account

    @api.multi
    def do_checks_operations(self, vals=None, cancel=False):
        """
        Check attached .ods file on this module to understand checks workflows
        This method is called from:
        * cancellation of payment to execute delete the right operation and
            unlink check if needed
        * from _get_liquidity_move_line_vals to add check operation and, if
            needded, change payment vals and/or create check and
        """
        self.ensure_one()
        rec = self
        if not rec.check_type:
            # continue
            return vals
        if (
                rec.payment_method_code == 'received_third_check' and
                rec.payment_type == 'inbound' and
                rec.partner_type == 'customer'):
            operation = 'holding'
            if cancel:
                _logger.info('Cancel Receive Check')
                rec.check_ids._del_operation(operation)
                rec.check_ids.unlink()
                return None

            _logger.info('Receive Check')
            self.create_check('third_check', operation, self.check_bank_id)
            vals['date_maturity'] = self.check_payment_date
            vals['account_id'] = self.get_third_check_account().id
        elif (
                rec.payment_method_code == 'delivered_third_check' and
                rec.payment_type == 'transfer' and
                rec.destination_journal_id.type == 'cash'):
            operation = 'selled'
            if cancel:
                _logger.info('Cancel Sell Check')
                rec.check_ids._del_operation(operation)
                return None

            _logger.info('Sell Check')
            rec.check_ids._add_operation(
                operation, rec, False)
            vals['account_id'] = self.get_third_check_account().id
        elif (
                rec.payment_method_code == 'delivered_third_check' and
                rec.payment_type == 'transfer' and
                rec.destination_journal_id.type == 'bank'):
            operation = 'deposited'
            if cancel:
                _logger.info('Cancel Deposit Check')
                rec.check_ids._del_operation(operation)
                return None

            _logger.info('Deposit Check')
            rec.check_ids._add_operation(
                operation, rec, False)
            vals['account_id'] = self.get_third_check_account().id
        elif (
                rec.payment_method_code == 'delivered_third_check' and
                rec.payment_type == 'outbound' and
                rec.partner_type == 'supplier'):
            operation = 'delivered'
            if cancel:
                _logger.info('Cancel Deliver Check')
                rec.check_ids._del_operation(operation)
                return None

            _logger.info('Deliver Check')
            rec.check_ids._add_operation(
                operation, rec, rec.partner_id)
            vals['account_id'] = self.get_third_check_account().id
        elif (
                rec.payment_method_code == 'issue_check' and
                rec.payment_type == 'outbound' and
                rec.partner_type == 'supplier'):
            operation = 'handed'
            if cancel:
                _logger.info('Cancel Hand Check')
                rec.check_ids._del_operation(operation)
                rec.check_ids.unlink()
                return None

            _logger.info('Hand Check')
            self.create_check('issue_check', operation, self.check_bank_id)
            vals['date_maturity'] = self.check_payment_date
            # if check is deferred, change account
            if self.check_subtype == 'deferred':
                vals['account_id'] = self.company_id._get_check_account(
                    'deferred').id
        elif (
                rec.payment_method_code == 'issue_check' and
                rec.payment_type == 'transfer' and
                rec.destination_journal_id.type == 'cash'):
            operation = 'withdrawed'
            if cancel:
                _logger.info('Cancel Withdrawal Check')
                rec.check_ids._del_operation(operation)
                rec.check_ids.unlink()
                return None

            _logger.info('Hand Check')
            self.create_check('issue_check', operation, self.check_bank_id)
            vals['date_maturity'] = self.check_payment_date
            # if check is deferred, change account
            # si retiramos por caja directamente lo sacamos de banco
            # if self.check_subtype == 'deferred':
            #     vals['account_id'] = self.company_id._get_check_account(
            #         'deferred').id
        else:
            raise UserError(_(
                'This operatios is not implemented for checks:\n'
                '* Payment type: %s\n'
                '* Partner type: %s\n'
                '* Payment method: %s\n'
                '* Destination journal: %s\n' % (
                    rec.payment_type,
                    rec.partner_type,
                    rec.payment_method_code,
                    rec.destination_journal_id.type)))
        return vals

    # @api.multi
    # def post(self):
    #     self.do_checks_operations()
    #     return super(AccountPayment, self).post()

    def _get_liquidity_move_line_vals(self, amount):
        vals = super(AccountPayment, self)._get_liquidity_move_line_vals(
            amount)
        vals = self.do_checks_operations(vals=vals)
        # if self.check_type:
        #     vals['date_maturity'] = self.check_payment_date
        #     if self.check_subtype == 'deferred':
        #         deferred_account = self.company_id.deferred_check_account_id
        #         if not deferred_account:
        #             raise UserError(_(
        #                 'No checks deferred account defined for company %s'
        #             ) % self.company_id.name)
        #         vals['account_id'] = deferred_account.id
        #     # vals['check_bank_id'] = self.check_bank_id.id
        #     # vals['check_owner_name'] = self.check_owner_name
        #     # vals['check_owner_vat'] = self.check_owner_vat
        #     # vals['check_number'] = self.check_number
        #     # vals['checkbook_id'] = self.checkbook_id.id
        #     # vals['check_issue_date'] = self.check_issue_date
        #     # if self.payment_method_code == 'issue_check':
        #     #     vals['check_type'] = 'issue_check'
        #     # else:
        #     #     vals['check_type'] = 'third_check'
        return vals
class AccountDebtLine(models.Model):
    _name = "account.debt.line"
    _description = "Account Debt Line"
    _auto = False
    _rec_name = 'document_number'
    _order = 'date asc, date_maturity asc, document_number asc, id'
    _depends = {
        'res.partner': [
            'user_id',
        ],
        'account.move': [
            'document_type_id', 'document_number',
        ],
        'account.move.line': [
            'account_id', 'debit', 'credit', 'date_maturity', 'partner_id',
            'amount_currency',
        ],
    }

    blocked = fields.Boolean(
        string='No Follow-up',
        readonly=True,
        help="You can check this box to mark this journal item as a "
        "litigation with the associated partner"
    )
    document_type_id = fields.Many2one(
        'account.document.type',
        'Document Type',
        readonly=True
    )
    document_number = fields.Char(
        readonly=True,
        string='Document Number',
    )
    date = fields.Date(
        readonly=True
    )
    date_maturity = fields.Date(
        readonly=True
    )
    ref = fields.Char(
        'Reference',
        readonly=True
    )
    amount = fields.Monetary(
        readonly=True,
        currency_field='company_currency_id',
    )
    amount_residual = fields.Monetary(
        readonly=True,
        string='Residual Amount',
        currency_field='company_currency_id',
    )
    currency_id = fields.Many2one(
        'res.currency',
        'Currency',
        readonly=True
    )
    amount_currency = fields.Monetary(
        readonly=True,
        currency_field='currency_id',
    )
    amount_residual_currency = fields.Monetary(
        readonly=True,
        string='Residual Amount in Currency',
        currency_field='currency_id',
    )
    move_lines_str = fields.Char(
        'Entry Lines String',
        readonly=True
    )
    account_id = fields.Many2one(
        'account.account',
        'Account',
        readonly=True
    )
    internal_type = fields.Selection([
        ('receivable', 'Receivable'),
        ('payable', 'Payable')],
        'Type',
        readonly=True,
    )
    # move_state = fields.Selection(
    #     [('draft', 'Unposted'), ('posted', 'Posted')],
    #     'Status',
    #     readonly=True
    # )
    reconciled = fields.Boolean(
    )
    partner_id = fields.Many2one(
        'res.partner',
        'Partner',
        readonly=True
    )
    account_type = fields.Many2one(
        'account.account.type',
        'Account Type',
        readonly=True
    )
    company_id = fields.Many2one(
        'res.company',
        'Company',
        readonly=True
    )

    # computed fields
    financial_amount = fields.Monetary(
        compute='_compute_move_lines_data',
        currency_field='company_currency_id',
    )
    financial_amount_residual = fields.Monetary(
        compute='_compute_move_lines_data',
        currency_field='company_currency_id',
    )
    # we get this line to make it easier to compute other lines
    # for debt lines, as we group by due date, we should have only one
    move_line_id = fields.Many2one(
        'account.move.line',
        string='Entry line',
        compute='_compute_move_lines_data',
    )
    move_line_ids = fields.One2many(
        'account.move.line',
        string='Entry lines',
        compute='_compute_move_lines_data',
    )
    move_id = fields.Many2one(
        'account.move',
        string='Entry',
        compute='_compute_move_lines_data',
    )
    move_ids = fields.One2many(
        'account.move',
        string='Entries',
        compute='_compute_move_lines_data',
    )
    company_currency_id = fields.Many2one(
        related='company_id.currency_id',
        readonly=True,
    )
    payment_group_id = fields.Many2one(
        'account.payment.group',
        'Payment Group',
        compute='_compute_move_lines_data',
    )
    invoice_id = fields.Many2one(
        'account.invoice',
        'Invoice',
        compute='_compute_move_lines_data',
    )
    # es una concatenacion de los name de los move lines
    name = fields.Char(
        compute='_compute_move_lines_data',
    )
    statement_id = fields.Many2one(
        'account.bank.statement',
        'Statement',
        compute='_compute_move_lines_data',
    )

    # TODO por ahora, y si nadie lo extraña, vamos a usar document_number
    # en vez de este, alternativas por si se extraña:
    # si se extraña entonces tal vez mejor restaurarlo con otro nombre
    # @api.one
    # def get_display_name(self):
    #     # usamos display_name para que contenga doc number o name
    #     # luego si el ref es igual al name del move no lo mostramos
    #     display_name = self.move_id.display_name
    #     ref = False
    #     # because account voucher replace / with ''
    #     move_names = [self.move_id.name, self.move_id.name.replace('/', '')]
    #     # solo agregamos el ref del asiento o el name del line si son
    #     # distintos a el name del asiento
    #     if self.ref and self.ref not in move_names:
    #         ref = self.ref
    #     elif (
    #             self.move_line_id.name and
    #             self.move_line_id.name != '/' and
    #             self.move_line_id.name not in move_names):
    #         ref = self.move_line_id.name
    #     if ref:
    #         display_name = '%s (%s)' % (display_name, ref)
    #     self.display_name = display_name

    @api.multi
    @api.depends('move_lines_str')
    # @api.depends('amount', 'amount_currency')
    def _compute_move_lines_data(self):
        """
        If debt_together in context then we discount payables and make
        cumulative all together
        """
        for rec in self:
            move_lines = rec.move_line_ids.browse(
                literal_eval(rec.move_lines_str))

            rec.move_line_ids = move_lines
            rec.name = ', '.join(move_lines.mapped('name'))
            rec.move_ids = rec.move_line_ids.mapped('move_id')
            if len(rec.move_ids) == 1:
                rec.move_id = rec.move_ids

            if len(move_lines) == 1:
                # return one line or empty recordset
                rec.move_line_id = (
                    len(move_lines) == 1 and move_lines[0] or
                    rec.env['account.move.line'])

            rec.invoice_id = rec.move_line_id.invoice_id
            rec.payment_group_id = rec.move_line_id.mapped(
                'payment_id.payment_group_id')
            rec.statement_id = rec.move_line_id.statement_id
            # invoices = rec.move_line_ids.mapped('invoice_id')
            # if len(invoices) == 1:
            #     rec.invoice_id = invoices

            # payment_groups = rec.move_line_ids.mapped(
            #     'payment_id.payment_group_id')
            # if len(payment_groups) == 1:
            #     rec.payment_group_id = payment_groups

            # statements = rec.move_line_ids.mapped('statement_id')
            # if len(statements) == 1:
            #     rec.statement_id = statements

            rec.financial_amount = sum(
                rec.move_line_ids.mapped('financial_amount'))
            rec.financial_amount_residual = sum(
                rec.move_line_ids.mapped('financial_amount_residual'))

    def init(self, cr):
        tools.drop_view_if_exists(cr, self._table)
        date_maturity_type = self.pool['ir.config_parameter'].get_param(
            cr, 1, 'account_debt_management.date_maturity_type')
        if date_maturity_type == 'detail':
            params = ('l.date_maturity as date_maturity,', ', l.date_maturity')
        elif date_maturity_type == 'max':
            params = ('max(l.date_maturity) as date_maturity,', '')
        else:
            params = ('min(l.date_maturity) as date_maturity,', '')
        query = """
            SELECT
                -- es una funcion y se renumera constantemente, por eso
                -- necesita el over
                -- ROW_NUMBER() OVER (ORDER BY l.partner_id, am.company_id,
                --     l.account_id, l.currency_id, a.internal_type,
                --     a.user_type_id, c.document_number, am.document_type_id,
                --     l.date_maturity) as id,
                -- igualmente los move lines son unicos, usamos eso como id
                max(l.id) as id,
                string_agg(cast(l.id as varchar), ',') as move_lines_str,
                max(am.date) as date,
                %s
                am.document_type_id as document_type_id,
                c.document_number as document_number,
                bool_and(l.reconciled) as reconciled,
                -- l.blocked as blocked,
                -- si cualquier deuda esta bloqueada de un comprobante,
                -- toda deberia estar bloqueda
                bool_and(l.blocked) as blocked,

                -- TODO borrar, al final no pudimos hacerlo asi porque si no
                -- agrupamos por am.name, entonces todo lo que no tenga tipo
                -- de doc lo muestra en una linea. Y si lo agregamos nos quedan
                -- desagregados los multiples pagos (y otros similares)
                -- si devuelve '' el concat del prefix y number lo cambiamos
                -- por null y luego coalesce se encarga de elerig el name
                -- devolvemos el string_agg de am.name para no tener que
                -- agregarlo en la clausula del group by
                -- COALESCE(NULLIF(CONCAT(
                --     dt.doc_code_prefix, am.document_number), ''),
                --         string_agg(am.name, ',')) as document_number,

                string_agg(am.ref, ',') as ref,
                --am.state as move_state,
                --l.full_reconcile_id as full_reconcile_id,
                --l.reconciled as reconciled,
                -- l.reconcile_partial_id as reconcile_partial_id,
                l.partner_id as partner_id,
                am.company_id as company_id,
                a.internal_type as internal_type,
                -- am.journal_id as journal_id,
                -- p.fiscalyear_id as fiscalyear_id,
                -- am.period_id as period_id,
                l.account_id as account_id,
                --l.analytic_account_id as analytic_account_id,
                -- a.internal_type as type,
                a.user_type_id as account_type,
                l.currency_id as currency_id,
                sum(l.amount_currency) as amount_currency,
                sum(l.amount_residual_currency) as amount_residual_currency,
                sum(l.amount_residual) as amount_residual,
                --pa.user_id as user_id,
                sum(l.balance) as amount
                -- coalesce(l.debit, 0.0) - coalesce(l.credit, 0.0) as amount
            FROM
                account_move_line l
                left join account_account a on (l.account_id = a.id)
                left join account_move am on (am.id=l.move_id)
                -- left join account_period p on (am.period_id=p.id)
                -- left join res_partner pa on (l.partner_id=pa.id)
                left join account_document_type dt on (
                    am.document_type_id=dt.id)
                left join (
                    SELECT
                        COALESCE (NULLIF (CONCAT (
                            dt.doc_code_prefix, am.document_number), ''),
                            am.name) as document_number,
                        am.id
                    FROM
                        account_move am
                        left join account_document_type dt on (
                            am.document_type_id=dt.id)
                    ) c on l.move_id = c.id
            WHERE
                -- l.state != 'draft' and
                a.internal_type IN ('payable', 'receivable')
            GROUP BY
                l.partner_id, am.company_id, l.account_id, l.currency_id,
                a.internal_type, a.user_type_id, c.document_number,
                am.document_type_id %s
                -- dt.doc_code_prefix, am.document_number
        """ % params
        cr.execute("""CREATE or REPLACE VIEW %s as (%s
        )""" % (self._table, query))

    @api.multi
    def action_open_related_document(self):
        self.ensure_one()
        # usamos lo que ya se usa en js para devolver la accion
        res_model, res_id, action_name, view_id = self.get_model_id_and_name()

        return {
            'type': 'ir.actions.act_window',
            'name': action_name,
            'res_model': res_model,
            'view_type': 'form',
            'view_mode': 'form',
            'views': [[view_id, 'form']],
            'res_id': res_id,
            # 'view_id': res[0],
        }

    @api.multi
    def get_model_id_and_name(self):
        """
        Function used to display the right action on journal items on dropdown
        lists, in reports like general ledger
        """
        if self.statement_id:
            return [
                'account.bank.statement', self.statement_id.id,
                _('View Bank Statement'), False]
        if self.payment_group_id:
            return [
                'account.payment.group',
                self.payment_group_id.id,
                _('View Payment Group'), False]
        if self.invoice_id:
            view_id = self.invoice_id.get_formview_id()
            return [
                'account.invoice',
                self.invoice_id.id,
                _('View Invoice'),
                view_id]
        # TODO ver si implementamos que pasa cuando hay mas de un move
        return [
            'account.move',
            self.move_id.id,
            _('View Move'),
            False]
Beispiel #6
0
class PaymentRequest(models.Model):

    _name = 'payment.request'
    _description = 'Payment Request'
    _inherit = ['mail.thread', 'ir.needaction_mixin']

    @api.model
    def _company_get(self):
        company_id = self.env['res.company']._company_default_get(self._name)
        return self.env['res.company'].browse(company_id.id)
    
    @api.model
    def _default_currency(self):
        company = self.env['res.company']._company_default_get('payment.request')
        return company.currency_id

    @api.model
    def _get_default_requested_by(self):
        return self.env['res.users'].browse(self.env.uid)
    
    @api.model
    @api.depends('requested_by')
    def _get_default_employee(self):
        
        requested_by = self.requested_by.id
        #self.employee_id = 
        self.env.cr.execute("SELECT hr_employee.id FROM hr_employee inner join resource_resource on hr_employee.resource_id = resource_resource.id where user_id ='%s'"  %(self.requested_by.id))
        res = self.env.cr.fetchone()[0]
        self.employee_id = res

    @api.model
    def _get_default_name(self):
        return self.env['ir.sequence'].get('payment.request')

    @api.model
    def _get_default_origin(self):
        for rec in self:
            rec.origin = rec.name

    @api.model
    def _default_picking_type(self):
        type_obj = self.env['stock.picking.type']
        company_id = self.env.context.get('company_id') or \
            self.env.user.company_id.id
        types = type_obj.search([('code', '=', 'incoming'),
                                 ('warehouse_id.company_id', '=', company_id)])
        if not types:
            types = type_obj.search([('code', '=', 'incoming'),
                                     ('warehouse_id', '=', False)])
        return types[:1]

    @api.multi
    @api.depends('state')
    def _compute_is_editable(self):
        for rec in self:
            if rec.state in (2,3,4,5,7):
                rec.is_editable = False
            else:
                rec.is_editable = True

    @api.multi
    def _track_subtype(self, init_values):
        for rec in self:
            if 'state' in init_values and rec.state == 2:
                return 'purchase_request.mt_request_to_approve'
            elif 'state' in init_values and rec.state == 3:
                return 'purchase_request.mt_request_to_department_manager_approved'
            elif 'state' in init_values and rec.state == 4:
                return 'purchase_request.mt_request_to_accountant_manager_approved'
            elif 'state' in init_values and rec.state == 5:
                return 'purchase_request.mt_request_approved'
            elif 'state' in init_values and rec.state == 7:
                return 'purchase_request.mt_request_rejected'
        return super(PaymentRequest, self)._track_subtype(init_values)
    
    @api.onchange('state', 'partner_id')
    def _onchange_allowed_purchase_ids(self):
        '''
        The purpose of the method is to define a domain for the available
        purchase orders.
        '''
        result = {}

        # A PO can be selected only if at least one PO line is not already in the invoice
        purchase_line_ids = self.line_ids.mapped('purchase_line_id')
        purchase_ids = self.line_ids.mapped('purchase_id').filtered(lambda r: r.order_line <= purchase_line_ids)

        result['domain'] = {'purchase_id': [
            ('invoice_status', '=', 'to invoice'),
            ('partner_id', 'child_of', self.partner_id.id),
            ('id', 'not in', purchase_ids.ids),
            ]}
        return result
    
    
    def _prepare_invoice_line_from_po_line(self, line):
        if line.product_id.purchase_method == 'purchase':
            qty = line.product_qty - line.qty_invoiced
        else:
            qty = line.qty_received - line.qty_invoiced
        if float_compare(qty, 0.0, precision_rounding=line.product_uom.rounding) <= 0:
            qty = 0.0
        taxes = line.taxes_id
        invoice_line_tax_ids = self.purchase_id.fiscal_position_id.map_tax(taxes)
        invoice_line = self.env['payment.request.line']
        data = {
            'purchase_line_id': line.id,
            'name': line.name,
            'origin': self.purchase_id.origin,
            'uom_id': line.product_uom.id,
            'product_id': line.product_id.id,
            #'account_id': invoice_line.with_context({'journal_id': self.journal_id.id, 'type': 'in_invoice'})._default_account(),
            #'price_unit': line.order_id.currency_id.compute(line.price_unit, self.currency_id, round=False),
            'product_price': line.order_id.currency_id.compute(line.price_unit, self.currency_id, round=False),
            #'quantity': qty,
            'product_qty':  line.product_qty,
            'discount': 0.0,
            'account_analytic_id': line.account_analytic_id.id,
            'invoice_line_tax_ids': invoice_line_tax_ids.ids
        }
        #account = invoice_line.get_invoice_line_account('in_invoice', line.product_id, self.purchase_id.fiscal_position_id, self.env.user.company_id)
        account = 0
        if account:
            data['account_id'] = account.id
        return data

    # Load all unsold PO lines
    @api.onchange('purchase_id')
    def purchase_order_change(self):
        if not self.purchase_id:
            return {}
        if not self.partner_id:
            self.partner_id = self.purchase_id.partner_id.id

        new_lines = self.env['payment.request.line']
        for line in self.purchase_id.order_line - self.line_ids.mapped('purchase_line_id'):
            data = self._prepare_invoice_line_from_po_line(line)
            new_line = new_lines.new(data)
            new_line._set_additional_fields(self)
            new_lines += new_line

        self.line_ids += new_lines
        self.purchase_id = False
        return {}

    
    name = fields.Char('Payment Reference', size=32, required=True,
                       default=_get_default_name,track_visibility='onchange')
    
    purchase_id = fields.Many2one('purchase.order', string='Add Purchase Order',
        help='Encoding help. When selected, the associated purchase order lines are added to the vendor bill. Several PO can be selected.')
    partner_id = fields.Many2one('res.partner', string='Supplier Name', change_default=True,
        required=True,
        track_visibility='always')
    
    origin = fields.Char('Source Document', size=32, compute=_get_default_origin)
    reject_reason = fields.Char('reject_reason', default=' ')
    date_start = fields.Date('Creation date',
                             help="Date when the user initiated the request.",
                             default=fields.Date.context_today,
                             track_visibility='onchange')
    requested_by = fields.Many2one('res.users','Requestor',required=True,
                                   track_visibility='onchange',
                                   default=_get_default_requested_by)
    employee_id = fields.Many2one('hr.employee', string='Staff ID',
                                   compute="_get_default_employee")
    employee_position = fields.Many2one('hr.job', string='Position',
                                   related='employee_id.job_id')
    assigned_to = fields.Many2one('res.users', 'Head of Department',
                                  domain=[('x_department_manager', '=', True)],
                                  track_visibility='onchange')
    assigned_to_2 = fields.Many2one('res.users', 'Head of Department',
                                  domain=[('x_department_manager', '=', True)],
                                  track_visibility='onchange')
    assigned_to_3 = fields.Many2one('res.users', 'Project Officer',
                                  domain=[('x_project_manager', '=', True)],
                                  track_visibility='onchange')
    description = fields.Text('Purpose of Request')
    direct_manager_notes = fields.Text('Direct manager notes')
    department_manager_notes = fields.Text('Department manager notes')
    #purchase_manager_notes = fields.Text('Purchases manager notes')
    #warehouse_manager_notes = fields.Text('Warehouse manager notes')
    accountant_manager_notes = fields.Text('CFO notes')
    executive_manager_notes = fields.Text('Executive manager notes')
    treasurer_manager_notes = fields.Text('Treasurer notes')
    budget_holder_notes = fields.Text('Budget Holder notes')
    company_id = fields.Many2one('res.company', 'Company',required=True,
                                 default=_company_get, track_visibility='onchange')
    line_ids = fields.One2many('payment.request.line', 'request_id','Expense Details', readonly=False, copy=True,track_visibility='onchange')
    state = fields.Selection(selection=_STATES,string='Status',index=True,
                             track_visibility='onchange',copy=False,default=1)
    stage = fields.Selection(selection=_STAGES,default=1,string='Stage')
    request_type = fields.Selection(selection=_TYPES,default=1,string='Request Type',
                                    help = 'Provide request as soon as possible :عاجل')
    type_reason = fields.Text('Reason')
    explain_type = fields.Char('Explain',compute='_compute_explain')
    department = fields.Many2one('hr.department', 'Department',
                                 track_visibility='onchange')
    project = fields.Many2one('hr.department', 'Project/Section',
                                  track_visibility='onchange')
    is_editable = fields.Boolean(string="Is editable",compute="_compute_is_editable",
                                 readonly=True)
    
    #ontime = fields.Integer(string='On Time',compute="change_ontime",readonly=True)
    
    #ontime_stage = fields.Integer(string='On Time Stage',compute="change_ontime_stage",readonly=True)
    
    #done1= fields.Char(string='Done',compute="change_done_stage",default='early')
    
    #progress = fields.Float(string='Progress',compute='change_progress',readonly=True)
    
    color = fields.Integer('Color Index', compute="change_colore_on_kanban")
    currency_id = fields.Many2one('res.currency', string='Currency', required=True,
        track_visibility='onchange', ondelete='restrict',
        default=_default_currency)
    
    #amount_requested = fields.Float( string='Amount Requested')
    
    budget_id = fields.Many2one('crossovered.budget' , string='Budget', required=True)
    budget_drc = fields.Many2one('account.budget.post', string="DRC", required=True)
    budget_dea = fields.Many2one('crossovered.budget.lines', string='DEA', required=True)
    total_dea = fields.Float(related='budget_dea.planned_amount', string="DEA Amount")
    invoice_id = fields.Many2one('account.invoice', string='Invoice')
    invoice_partner = fields.Many2one('res.partner',related='invoice_id.partner_id', string="Invoice Partner")
    invoice_date = fields.Date(related='invoice_id.date_invoice', string="Invoice Date")
    invoice_amount = fields.Monetary(string="Invoice Total", related='invoice_id.amount_total')
    cheque = fields.Boolean(string="Cheque")
    bank_transfer = fields.Boolean(string="Bank Transfer")
    cash = fields.Boolean(string="Cash")
    cheque_number = fields.Char(string="Cheque Number")
    transfer_reference = fields.Char(string="Transfer Reference")
    #outstand_payment = fields.Boolean(string="Requestor does not already have an outstanding payment")
    #exceed_maximum = fields.Boolean(string="Advance does not exceed maximum allocation")
    coding_correct = fields.Boolean(string="Coding correct")
    authorised_dfa = fields.Boolean(string="Authorised per DFA")
    payee_correct = fields.Boolean(string="Payee Correct")
    Procurement_procedures = fields.Boolean(string='Procurement Procedures Followed')
    good_received = fields.Boolean(string='Good/Services Received')
    is_direct_notes = fields.Boolean(string="Is Direct Notes",
                                 compute="_compute_is_direct_notes",
                                 readonly=True)
    is_dept_notes = fields.Boolean(string="Is Dept Notes",
                                 compute="_compute_is_dept_notes",
                                 readonly=True)
    is_account_notes = fields.Boolean(string="Is Accountant Notes",
                                 compute="_compute_is_account_notes",
                                 readonly=True)
    is_treasure_notes = fields.Boolean(string="Is Treasure Notes",
                                 compute="_compute_is_treasure_notes",
                                 readonly=True)
    is_holder_notes = fields.Boolean(string="Is Holder Notes",
                                 compute="_compute_is_pres_notes",
                                 readonly=True)
    is_executive_notes = fields.Boolean(string="Is Executive Notes",
                                 compute="_compute_is_executive_notes",
                                 readonly=True)
    is_usr = fields.Boolean(string="Is Requested by User",
                                 compute="_compute_is_usr",
                                 readonly=True)
    is_stage = fields.Boolean(string="Is Stage User",
                                 compute="_compute_is_stage",
                                 readonly=True)
    is_required = fields.Boolean(string="Is Required User",
                                 compute="_compute_is_required",
                                 readonly=True)
    is_dept_approve = fields.Boolean(string="Is Dept Approve",
                                 compute="_compute_is_dept_approve",
                                 readonly=True)
    is_reject_required = fields.Boolean(string="Is Reject Required User")
    is_request_approval =  fields.Boolean(string="Is Request_Approval User",
                                 compute="_compute_is_request_approval",
                                 readonly=True)
    
    total_budget = fields.Float(compute='_compute_budget',store=True)
    total_drc = fields.Float(compute='_compute_drc',store=True)
    budget_availability = fields.Float(compute='_compute_budget_availability',store=True)
    drc_availability = fields.Float(compute='_compute_drc_availability',store=True)
    dea_availability = fields.Float(compute='_compute_dea_availability',store=True)
    
    price_alltotal = fields.Float( string='Total Price', compute='_compute_amount_all', store=True)
    
    is_draft = fields.Boolean(string="Is Draft", compute="_compute_is_draft",readonly=True)
    is_assign_direct_Manager = fields.Boolean(string="Is Assign Direct Manager", compute="_compute_is_assign_direct_Manager",readonly=True)
    is_assign_head_of_department = fields.Boolean(string="Is Assign Direct Manager", compute="_compute_is_assign_head_of_department",readonly=True)
    is_assign_cfo_manager = fields.Boolean(string="Is Assign CFO Manager", compute="_compute_is_assign_cfo_manager",readonly=True)
    is_cfo_approved = fields.Boolean(string='CFO Approved', compute='_compute_is_cfo_approved',readonly=True)
    is_approved = fields.Boolean(string='Approved',compute='_is_approved',readonly=True)
    is_rejected = fields.Boolean(string='Rejected',compute='_is_rejected',readonly=True)
    
    @api.depends('state')
    def _compute_is_draft(self):
        for record in self:
            if record.state == 1:
               record.is_draft = True
               
    @api.depends('state')
    def _compute_is_assign_direct_Manager(self):
        for record in self:
            if record.state == 2:
               record.is_assign_direct_Manager = True
               
    @api.depends('state')
    def _compute_is_assign_head_of_department(self):
        for record in self:
            if record.state == 3:
               record.is_assign_head_of_department = True
               
    @api.depends('state')
    def _compute_is_assign_cfo_manager(self):
        for record in self:
            if record.state == 4:
               record.is_assign_cfo_manager = True
               
    @api.depends('state')
    def _compute_is_cfo_approved(self):
        for record in self:
            if record.state == 5:
               record.is_cfo_approved = True
    
    @api.depends('state')
    def _is_approved(self):
        for record in self:
            if record.state == 6:
               record.is_approved = True
               
    @api.depends('state')
    def _is_rejected(self):
        for record in self:
            if record.state == 7:
               record.is_rejected = True
    
    @api.depends('budget_id')
    def _compute_budget(self):
        for record in self:
            record.total_budget = record.budget_id.total_cost
            print record.total_budget
    
    @api.depends('budget_drc')
    def _compute_drc(self):
        for record in self:
            record.total_drc = record.budget_drc.total_cost
            print record.total_drc
    
    @api.depends('budget_id')
    def _compute_budget_availability(self):
        for record in self:
            record.budget_availability = record.budget_id.total_availability
            print record.budget_availability
    
    @api.depends('budget_drc')
    def _compute_drc_availability(self):
        for record in self:
            record.drc_availability = record.budget_drc.total_availability
            print record.drc_availability
    
    @api.depends('budget_dea')
    def _compute_dea_availability(self):
        for record in self:
            record.dea_availability = record.budget_dea.budget_availability
            print record.dea_availability
        
    @api.multi
    @api.depends('requested_by')    
    def _compute_is_request_approval(self):
        for rec in self:
            #dep_mang = self.env['res.users'].browse(['res.users'].x_department_manager)
            usr = self.env['res.users'].browse(self.env.uid)
            if usr == rec.requested_by and rec.state == 1:
                rec.is_request_approval = True
            elif usr == rec.requested_by and rec.state != 1:
                rec.is_request_approval = False
            elif usr != rec.requested_by and rec.state == 1:
                rec.is_request_approval = False
            elif usr != rec.requested_by and rec.state != 1:
                rec.is_request_approval = False
            #elif 'res.user.x_department_manager' == True:
             #   rec.is_required = True
            else:
                    rec.is_request_approval = False
    @api.multi
    @api.depends('requested_by')            
    def _compute_is_required(self):
        for rec in self:
            #dep_mang = self.env['res.users'].browse(['res.users'].x_department_manager)
            usr = self.env['res.users'].browse(self.env.uid)
            if usr in rec.assigned_to :
                rec.is_required = True
            #elif 'res.user.x_department_manager' == True:
             #   rec.is_required = True
            else:
                rec.is_required = False
                
    @api.multi
    @api.depends('line_ids.price_total')
    def _compute_amount_all(self):
        for request in self:
            for line in request.line_ids: 
                request.price_alltotal += line.price_total
                
    @api.multi
    @api.depends('requested_by')
    def _compute_is_stage(self):
        for rec in self:
            usr = self.env['res.users'].browse(self.env.uid)
            if usr.has_group('__export__.res_groups_56'):
                rec.is_stage = True
            elif usr.has_group('__export__.res_groups_82'):
                rec.is_stage = True
            else:
                if usr.has_group('__export__.res_groups_59'):
                    rec.is_stage = True
                else:
                    rec.is_stage = False
                    
    @api.depends('request_type')
    def _compute_explain(self):
        for rec in self:
            if rec.request_type == 2:
                rec.explain_type = 'Provide request as soon as possible'
            elif rec.request_type == 3:
                rec.explain_type = 'For project and time conditions, the request is made exceptionally from the rules of the regulation'
            else:
                rec.explain_type = ''
    
    @api.multi
    @api.depends('requested_by')            
    def _compute_is_usr(self):
        for rec in self:
            if rec.requested_by == self.env['res.users'].browse(self.env.uid):
                rec.is_usr = True
            else:
                rec.is_usr = False

    @api.multi
    @api.depends('requested_by')
    def _compute_is_direct_notes(self):
        for rec in self:
            if rec.requested_by == self.env['res.users'].browse(self.env.uid):
                rec.is_direct_notes = True
            else:
                user = self.env['res.users'].browse(self.env.uid)
                if user.has_group('__export__.res_groups_62'):
                    rec.is_direct_notes = True
                else:
                    rec.is_direct_notes= False
    @api.multi
    @api.depends('requested_by')                
    def _compute_is_dept_notes(self):
        for rec in self:
            if rec.requested_by == self.env['res.users'].browse(self.env.uid):
                rec.is_dept_notes = True
            else:
                user = self.env['res.users'].browse(self.env.uid)
                if user.has_group('__export__.res_groups_61'):
                    rec.is_dept_notes = True
                else:
                    rec.is_dept_notes= False
    @api.multi
    @api.depends('requested_by')                
    def _compute_is_dept_approve(self):
        for rec in self:
            if rec.assigned_to == self.env['res.users'].browse(self.env.uid):
                rec.is_dept_approve = True
            else:
                rec.is_dept_approve = False
    
    @api.multi
    @api.depends('requested_by')                
    def _compute_is_account_notes(self):
        for rec in self:
            if rec.requested_by == self.env['res.users'].browse(self.env.uid):
                rec.is_account_notes = True
            else:
                user = self.env['res.users'].browse(self.env.uid)
                if user.has_group('__export__.res_groups_59'):
                    rec.is_account_notes = True
                else:
                    rec.is_account_notes= False
    
    @api.multi
    @api.depends('requested_by')                
    def _compute_is_treasure_notes(self):
        for rec in self:
            if rec.requested_by == self.env['res.users'].browse(self.env.uid):
                rec.is_treasure_notes = True
            else:
                user = self.env['res.users'].browse(self.env.uid)
                if user.has_group('__export__.res_groups_63'):
                    rec.is_treasure_notes = True
                else:
                    rec.is_treasure_notes= False
    @api.multi
    @api.depends('requested_by')
    def _compute_is_pres_notes(self):
        for rec in self:
            if rec.requested_by == self.env['res.users'].browse(self.env.uid):
                rec.is_pres_notes = True
            else:
                user = self.env['res.users'].browse(self.env.uid)
                if user.has_group('__export__.res_groups_65'):
                    rec.is_pres_notes = True
                else:
                    rec.is_pres_notes= False
    @api.multi
    @api.depends('requested_by')
    def _compute_is_executive_notes(self):
        for rec in self:
            if rec.requested_by == self.env['res.users'].browse(self.env.uid):
                rec.is_executive_notes = True
            else:
                user = self.env['res.users'].browse(self.env.uid)
                if user.has_group('purchase_request.group_purchase_request_manager'):
                    rec.is_executive_notes = True
                else:
                    rec.is_executive_notes= False
    
    @api.multi
    def copy(self, default=None):
        default = dict(default or {})
        self.ensure_one()
        default.update({
            'state': '1',
            'name': self.env['ir.sequence'].get('payment.request'),
        })
        return super(PaymentRequest, self).copy(default)

    @api.model
    def create(self, vals):
        request = super(PaymentRequest, self).create(vals)
        if vals.get('assigned_to'):
            request.message_subscribe_users(user_ids=[request.assigned_to.id])
        return request

    @api.multi
    def write(self, vals):
        res = super(PaymentRequest, self).write(vals)
        for request in self:
            if vals.get('assigned_to'):
                self.message_subscribe_users(user_ids=[request.assigned_to.id])
        return res

    @api.multi
    def button_draft(self):
        for rec in self:
            #rec.state = 'draft'
            #rec.stage = 1
            
            if self.state == 2 and self.direct_manager_notes != False:
                rec.state = 1
                rec.stage = 1
                rec.is_reject_required = False
                #raise ValidationError("You can reject request after write reason in notes:")
                #return False
            elif self.state == 3 and self.department_manager_notes != False:
                rec.state = 1
                rec.stage = 1
                rec.is_reject_required = False
                #raise ValidationError("You can reject request after write reason in notes:")
                #return False
            elif self.state == 4 and self.accountant_manager_notes != False:
                rec.state = 1
                rec.stage = 1
                rec.is_reject_required = False
                #raise ValidationError("You can reject request after write reason in notes:")
                #return False
            elif self.state == 5 and self.accountant_manager_notes != False:
                rec.state = 1
                rec.stage = 1
                rec.is_reject_required = False
            elif self.state == 6 and self.executive_manager_notes != False:
                rec.state = 1
                rec.stage = 1
                rec.is_reject_required = False
            elif self.state == 7 and self.accountant_manager_notes != False:
                rec.state = 1
                rec.stage = 1
                rec.is_reject_required = False
            else:
                if self.state == 2:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 3:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 4:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 5:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 6:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 7:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
        return True

    @api.multi
    def button_to_approve(self):
        for rec in self:
            if (rec.dea_availability - rec.price_alltotal) > 0:
                if rec.price_alltotal > 0:
                    if rec.assigned_to_3:
                        rec.state = 2
                        rec.stage = 2
                        rec.is_reject_required = False
                    elif rec.assigned_to_3 == 0:
                        rec.state = 3
                        rec.stage = 2
                        rec.is_reject_required = False
                    else:
                        if rec.assigned_to:
                            rec.state = 3
                            rec.stage = 2
                            rec.is_reject_required = False
                        elif rec.assigned_to == 0:
                            rec.state = 4
                            rec.stage = 4
                            rec.is_reject_required = False
                        else:
                            rec.state = 4
                            rec.stage = 4
                            rec.is_reject_required = False
                else:
                    rec.reject_reason = 'You Must Write price Items'
                    rec.is_reject_required = True
            else:
                 raise UserError(_(
                    "Cannot Request because budget not enough this request !"))
        return True
    
    @api.multi
    def button_to_department_manager_approved(self):
        for rec in self:
            if rec.price_alltotal < 5000:
                rec.state = 4
                rec.stage = 3
                rec.is_reject_required = False
            else:
                rec.state = 3
                rec.is_reject_required = False
        return True

    @api.multi
    def button_to_accountant_manager_approved(self):
        for rec in self:
            if rec.price_alltotal < 50000:
                rec.state = 4
                rec.stage = 3
                rec.is_reject_required = False
            else:
                rec.state = 4
                rec.stage = 3
                rec.is_reject_required = False
            #rec.state = 'to_accountant_manager_approved'
            #rec.stage = 3
            #rec.is_reject_required = False
        return True
    
    @api.multi
    def button_approved(self):
        for rec in self:
            if rec.price_alltotal < 50000:
                rec.state = 6
                rec.stage = 5
                rec.is_reject_required = False
            else:
                rec.state = 5
                rec.stage = 5
                rec.is_reject_required = False
            #rec.state = 'approved'
            #rec.stage = 5
            #rec.is_reject_required = False
        return True
    
    @api.multi
    def button_ceo_approved(self):
        for rec in self:
            rec.state = 5
            rec.stage = 5
            rec.is_reject_required = False
        return True

    @api.multi
    def button_rejected(self):
        for rec in self:
            if self.state == 2 and self.direct_manager_notes != False:
                rec.state = 7
                rec.stage = 7
                rec.is_reject_required = False
                #raise ValidationError("You can reject request after write reason in notes:")
                #return False
            elif self.state == 3 and self.department_manager_notes != False:
                rec.state = 7
                rec.stage = 7
                rec.is_reject_required = False
                #raise ValidationError("You can reject request after write reason in notes:")
                #return False
            elif self.state == 4 and self.accountant_manager_notes != False:
                rec.state = 7
                rec.stage = 7
                rec.is_reject_required = False
                #raise ValidationError("You can reject request after write reason in notes:")
                #return False
            elif self.state == 5 and self.accountant_manager_notes != False:
                rec.state = 7
                rec.stage = 7
                rec.is_reject_required = False
            elif self.state == 6 and self.executive_manager_notes != False:
                rec.state = 7
                rec.stage = 7
                rec.is_reject_required = False
            else:
                if self.state == 2:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 3:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 4:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 5:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
                elif self.state == 6:
                    rec.reject_reason = 'You Could Write Reject Reason In notes'
                    rec.is_reject_required = True
        return True
    
   # _constraints = [
    #    (button_rejected, '"You can reject request after write reason in notes :".'),
        
    #(legacy_doc1_getFilename, '"The file must be a JPG file".'),
#]
    @api.multi
    def _check_the_date(self):
        #for rec in self:
         #   d1 = datetime.datetime.strptime((rec.date_start),'%Y-%m-%d') 
          #  new_date = d1 + datetime.timedelta(days=18)
           # rec.date_finish = new_date
        for rec in self:
            if rec.price_alltotal <= 500 and rec.price_alltotal > 0 and (rec.state == 4 or rec.state == 5):
                d1 = datetime.datetime.strptime((rec.date_start),'%Y-%m-%d') 
                new_date = d1 + datetime.timedelta(days=6)
                rec.date_finish = new_date
            elif rec.price_alltotal > 500 and rec.price_alltotal <= 5000 and (rec.state == 4 or rec.state == 5):
                d1 = datetime.datetime.strptime((rec.date_start),'%Y-%m-%d') 
                new_date = d1 + datetime.timedelta(days=6)
                rec.date_finish = new_date
            elif rec.price_alltotal > 5000 and rec.price_alltotal <= 100000 and (rec.state == 4 or rec.state == 5):
                d1 = datetime.datetime.strptime((rec.date_start),'%Y-%m-%d') 
                new_date = d1 + datetime.timedelta(days=12)
                rec.date_finish = new_date
            elif rec.price_alltotal > 100000 and (rec.state == 4 or rec.state == 5):
                d1 = datetime.datetime.strptime((rec.date_start),'%Y-%m-%d') 
                new_date = d1 + datetime.timedelta(days=35)
                rec.date_finish = new_date
            else:
                d1 = datetime.datetime.strptime((rec.date_start),'%Y-%m-%d') 
                new_date = datetime.datetime.strptime((rec.date_start),'%Y-%m-%d')
                rec.date_finish = new_date

    def change_progress(self):
        for rec in self:
            x = rec.stage
            if rec.stage == 15:
                y = 0
            elif rec.stage == 1:
                y = 0
            else:
                y = x * 100 / 14
            rec.progress= y
    


    def change_ontime_stage(self):
        for rec in self:
            d1 = datetime.datetime.today()
            d2 = datetime.datetime.strptime((rec.date_start),'%Y-%m-%d') 
            d = str((d1-d2).days + 1)
            rec.ontime_stage = d
    def change_done_stage(self):
        for rec in self:
            if rec.price_alltotal <= 500:
                if rec.stage == 1 and rec.ontime_stage > 1:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==2 and rec.ontime_stage > 2:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==3 and rec.ontime_stage > 4:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==4 and rec.ontime_stage > 4:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==5 and rec.ontime_stage > 4:
                    dd = "late"
                    rec.done1 = dd
                #elif rec.stage ==6 and rec.ontime_stage > 4:
                 #   dd = "late"
                  #  rec.done1 = dd
            elif rec.price_alltotal > 500 and rec.price_alltotal <= 5000:
                if rec.stage == 1 and rec.ontime_stage > 1:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==2 and rec.ontime_stage > 2:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==3 and rec.ontime_stage > 5:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==4 and rec.ontime_stage > 5:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==5 and rec.ontime_stage > 5:
                    dd = "late"
                    rec.done1 = dd
                #elif rec.stage ==6 and rec.ontime_stage > 5:
                 #   dd = "late"
                  #  rec.done1 = dd
            elif rec.price_alltotal > 5000 and rec.price_alltotal <= 100000:
                if rec.stage == 1 and rec.ontime_stage > 1:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==2 and rec.ontime_stage > 2:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==3 and rec.ontime_stage > 5:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==4 and rec.ontime_stage > 5:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==5 and rec.ontime_stage > 5:
                    dd = "late"
                    rec.done1 = dd
                #elif rec.stage ==6 and rec.ontime_stage > 5:
                 #   dd = "late"
                  #  rec.done1 = dd
            elif rec.price_alltotal > 100000:
                if rec.stage == 1 and rec.ontime_stage > 1:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==2 and rec.ontime_stage > 2:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==3 and rec.ontime_stage > 7:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==4 and rec.ontime_stage > 7:
                    dd = "late"
                    rec.done1 = dd
                elif rec.stage ==5 and rec.ontime_stage > 7:
                    dd = "late"
                    rec.done1 = dd
                #elif rec.stage ==6 and rec.ontime_stage > 7:
                 #   dd = "late"
                  #  rec.done1 = dd
                elif (rec.stage ==6 or rec.stage ==7 or rec.stage ==8 or rec.stage ==9)  and rec.ontime_stage > 15:
                    dd = "late"
                    rec.done1 = dd
                elif (rec.stage ==10 or rec.stage ==11 or rec.stage ==12 or rec.stage ==13)  and rec.ontime_stage > 12:
                    dd = "late"
                    rec.done1 = dd

    def change_colore_on_kanban(self):   
        for record in self:
            color = 0
            if record.progress == 100:
                color = 5
            else:
                if record.ontime < 0:
                    color = 9
                elif record.ontime == 0:
                    if record.state == 4:
                        color = 3
                    elif record.state == 3:
                        color = 3
                    elif record.state == 7:
                        color = 6
                    else:
                        color = 3
                elif record.ontime > 0:
                    color = 5
                elif record.progress == 100:
                    color = 5
                elif record.state == 7:
                    color = 1
            record.color = color
            
    @api.multi
    def write(self,vals):
        # Your logic goes here or call your method
        super(PaymentRequest, self).write(vals)
        for ss in self:
            if ss.state == 7:
                ss.env.cr.execute("UPDATE payment_request SET stage = 15 WHERE id = '%s' " %(ss.id))
        return True    
Beispiel #7
0
class DonationLine(models.Model):
    _name = 'donation.line'
    _description = 'Donation Lines'
    _rec_name = 'product_id'

    @api.onchange('donation_id.donation_method')
    def onchange_donation_method(self):
        res = {}
        res['domain'] = {
            'product_id':
            [('donation_method', '=', donation_id.donation_method.id)]
        }
        return res

    @api.multi
    @api.depends('unit_price', 'quantity', 'donation_id.currency_id',
                 'donation_id.donation_date', 'donation_id.company_id')
    def _compute_amount_company_currency(self):
        for line in self:
            amount = line.quantity * line.unit_price
            line.amount = amount
            donation_currency = line.donation_id.currency_id.with_context(
                date=line.donation_id.donation_date)
            line.amount_company_currency = donation_currency.compute(
                amount, line.donation_id.company_id.currency_id)

    donation_id = fields.Many2one('donation.donation',
                                  string='Donation',
                                  ondelete='cascade')
    currency_id = fields.Many2one('res.currency',
                                  related='donation_id.currency_id',
                                  readonly=True)
    company_currency_id = fields.Many2one(
        'res.currency',
        related='donation_id.company_id.currency_id',
        readonly=True)
    product_id = fields.Many2one('product.product',
                                 domain=[('donation', '=', True)],
                                 string='Product',
                                 required=True,
                                 ondelete='restrict')
    #
    quantity = fields.Integer(string='Quantity', default=1)
    unit_price = fields.Monetary(string='Unit Price',
                                 digits=dp.get_precision('Account'),
                                 currency_field='currency_id')
    amount = fields.Monetary(compute='_compute_amount_company_currency',
                             string='Amount',
                             currency_field='currency_id',
                             digits=dp.get_precision('Account'),
                             store=True)
    amount_company_currency = fields.Monetary(
        compute='_compute_amount_company_currency',
        string='Amount in Company Currency',
        currency_field='company_currency_id',
        digits=dp.get_precision('Account'),
        store=True)
    analytic_account_id = fields.Many2one('account.analytic.account',
                                          string='Analytic Account',
                                          domain=[('account_type', '!=',
                                                   'closed')],
                                          ondelete='restrict')
    #campaign_id = fields.Many2one(
    analytic_account2 = fields.Many2one(
        'account.analytic.account',
        related='donation_id.campaign_id.analytic_account_id',
        string='Analytic Account',
        readonly=True,
        store=True)
    account_id = fields.Many2one(
        'account.account',
        related='donation_id.donation_place.account_id',
        string='Account',
        readonly=True,
        store=True)
    #analytic2 = fields.Integer(string = 'id', compute='get_analytic_account_id_3',store=True)
    #analytic3 = fields.Integer(string = 'id2',related='analytic_account_id.id' ,store=True)
    sequence = fields.Integer('Sequence')
    # for the fields tax_receipt_ok and in_kind, we made an important change
    # between v8 and v9: in v8, it was a reglar field set by an onchange
    # in v9, it is a related stored field
    tax_receipt_ok = fields.Boolean(related='product_id.tax_receipt_ok',
                                    readonly=True,
                                    store=True)
    donation_method = fields.Many2one(related='product_id.donation_method',
                                      readonly=True,
                                      store=True,
                                      string='Method')
    in_kind = fields.Boolean(related='product_id.in_kind_donation',
                             readonly=True,
                             store=True,
                             string='In Kind')
    #tags_id = fields.Many2many('account.analytic.tag',string = 'Tags')

    @api.onchange('product_id')
    def product_id_change(self):
        if self.product_id:
            # We should change that one day...
            if self.product_id.list_price:
                self.unit_price = self.product_id.list_price

            #self.env.cr.execute("INSERT INTO account_analytic_tag_donation_line_rel(donation_line_id, account_analytic_tag_id)VALUES ('%s', '%d')" %(self.id,))

    #@api.one
    #@api.depends('analytic_account_id', 'analytic_account2')
    @api.model
    #def get_analytic_account_id_3(self):
    #for rec in self:
    #rec.env.cr.execute("SELECT method.tag_id FROM donation_line inner join donation_instrument as method on donation_line.donation_method = method.id where donation_line.id= '%s'" %(rec.id))
    #res = rec.env.cr.fetchone()[0]
    #rec.env.cr.execute("insert INTO account_analytic_tag_donation_line_rel(donation_line_id, account_analytic_tag_id) VALUES ('%s','%s')" %(rec.id,res))11
    #rec.analytic_tag = 1
    #@api.one
    #@api.depends('analytic_account2')
    #@api.model
    #def get_analytic_account_id_2(self):
    #self.analytic2 = self.analytic_account_id.id
    #   return self.analytic_account2.id or False
    #return self.analytic_account_id.id or False

    @api.model
    def get_analytic_account_id(self):
        #self.analytic2 = self.analytic_account_id.id
        return self.analytic_account2.id or False
        #return self.analytic_account_id.id or False
    @api.model
    def get_account_id(self):
        #self.analytic2 = self.analytic_account_id.id
        return self.account_id.id or False
Beispiel #8
0
class AccountPaymentGroupInvoiceWizard(models.TransientModel):
    _name = "account.payment.group.invoice.wizard"

    @api.model
    def default_payment_group(self):
        return self.env['account.payment.group'].browse(
            self._context.get('active_id', False))

    payment_group_id = fields.Many2one(
        'account.payment.group',
        default=default_payment_group,
        ondelete='cascade',
        required=True,
    )
    journal_id = fields.Many2one(
        'account.journal',
        'Journal',
        required=True,
        ondelete='cascade',
    )
    date_invoice = fields.Date(string='Refund Date',
                               default=fields.Date.context_today,
                               required=True)
    currency_id = fields.Many2one(
        related='payment_group_id.currency_id',
        readonly=True,
    )
    date = fields.Date(string='Accounting Date')
    product_id = fields.Many2one(
        'product.product',
        required=True,
        domain=[('sale_ok', '=', True)],
    )
    tax_ids = fields.Many2many(
        'account.tax',
        string='Taxes',
    )
    amount_untaxed = fields.Monetary(
        string='Untaxed Amount',
        required=True,
        compute='_compute_amount_untaxed',
        inverse='_inverse_amount_untaxed',
    )
    # we make amount total the main one and the other computed because the
    # normal case of use would be to know the total amount and also this amount
    # is the suggested one on creating the wizard
    amount_total = fields.Monetary(string='Total Amount', required=True)
    description = fields.Char(string='Reason', )
    company_id = fields.Many2one(
        related='payment_group_id.company_id',
        readonly=True,
    )
    account_analytic_id = fields.Many2one(
        'account.analytic.account',
        'Analytic Account',
    )

    @api.onchange('product_id')
    def change_product(self):
        self.ensure_one()
        if self.payment_group_id.partner_type == 'purchase':
            taxes = self.product_id.supplier_taxes_id
        else:
            taxes = self.product_id.taxes_id
        company = self.company_id or self.env.user.company_id
        taxes = taxes.filtered(lambda r: r.company_id == company)
        self.tax_ids = self.payment_group_id.partner_id.with_context(
            force_company=company.id).property_account_position_id.map_tax(
                taxes)

    @api.onchange('amount_untaxed', 'tax_ids')
    def _inverse_amount_untaxed(self):
        self.ensure_one()
        if self.tax_ids:
            taxes = self.tax_ids.compute_all(
                self.amount_untaxed,
                self.company_id.currency_id,
                1.0,
                product=self.product_id,
                partner=self.payment_group_id.partner_id)
            self.amount_total = taxes['total_included']
        else:
            self.amount_total = self.amount_untaxed

    @api.depends('tax_ids', 'amount_total')
    def _compute_amount_untaxed(self):
        """
        For now we implement inverse only for percent taxes. We could extend to
        other by simulating tax.price_include = True, computing tax and
        then restoring tax.price_include = False.
        """
        self.ensure_one()
        if any(x.amount_type != 'percent' for x in self.tax_ids):
            raise ValidationError(
                _('You can only set amount total if taxes are of type '
                  'percentage'))
        tax_percent = sum(
            [x.amount for x in self.tax_ids if not x.price_include])
        total_percent = (1 + tax_percent / 100) or 1.0
        self.amount_untaxed = self.amount_total / total_percent

    @api.onchange('payment_group_id')
    def change_payment_group(self):
        journal_type = 'sale'
        type_tax_use = 'sale'
        if self.payment_group_id.partner_type == 'purchase':
            journal_type = 'purchase'
            type_tax_use = 'purchase'
        journal_domain = [
            ('type', '=', journal_type),
            ('company_id', '=', self.payment_group_id.company_id.id),
        ]
        tax_domain = [('type_tax_use', '=', type_tax_use),
                      ('company_id', '=', self.payment_group_id.company_id.id)]
        self.journal_id = self.env['account.journal'].search(journal_domain,
                                                             limit=1)
        # usually debit/credit note will be for the payment difference
        self.amount_total = abs(self.payment_group_id.payment_difference)
        return {
            'domain': {
                'journal_id': journal_domain,
                'tax_ids': tax_domain,
            }
        }

    @api.multi
    def get_invoice_vals(self):
        self.ensure_one()
        payment_group = self.payment_group_id
        if payment_group.partner_type == 'supplier':
            invoice_type = 'in_'
        else:
            invoice_type = 'out_'

        if self._context.get('refund'):
            invoice_type += 'refund'
        else:
            invoice_type += 'invoice'

        return {
            'name': self.description,
            'date': self.date,
            'date_invoice': self.date_invoice,
            'origin': _('Payment id %s') % payment_group.id,
            'journal_id': self.journal_id.id,
            'user_id': payment_group.partner_id.user_id.id,
            'partner_id': payment_group.partner_id.id,
            'type': invoice_type,
            # 'invoice_line_ids': [('invoice_type')],
        }

    @api.multi
    def confirm(self):
        self.ensure_one()

        invoice = self.env['account.invoice'].create(self.get_invoice_vals())

        inv_line_vals = {
            'product_id': self.product_id.id,
            'price_unit': self.amount_untaxed,
            'invoice_id': invoice.id,
            'invoice_line_tax_ids': [(6, 0, self.tax_ids.ids)],
        }
        invoice_line = self.env['account.invoice.line'].new(inv_line_vals)
        invoice_line._onchange_product_id()
        # restore chosen taxes (changed by _onchange_product_id)
        invoice_line.invoice_line_tax_ids = self.tax_ids
        line_values = invoice_line._convert_to_write(invoice_line._cache)
        line_values['price_unit'] = self.amount_untaxed
        if self.account_analytic_id:
            line_values['account_analytic_id'] = self.account_analytic_id.id
        invoice.write({'invoice_line_ids': [(0, 0, line_values)]})
        invoice.compute_taxes()
        invoice.signal_workflow('invoice_open')

        # TODO this FIX is to be removed on v11. This is to fix reconcilation
        # of the invoice with secondary_currency and the debit note without
        # secondary_currency
        secondary_currency = self.payment_group_id.to_pay_move_line_ids.mapped(
            'currency_id')
        if len(secondary_currency) == 1:
            move_lines = invoice.move_id.line_ids.filtered(
                lambda x: x.account_id == invoice.account_id)
            # use _write because move is already posted
            if not move_lines.mapped('currency_id'):
                move_lines._write({
                    'currency_id': secondary_currency.id,
                    'amount_currency': 0.0,
                })

        self.payment_group_id.to_pay_move_line_ids += (
            invoice.open_move_line_ids)
Beispiel #9
0
class PurchaseOrder(models.Model):
    _name = "purchase.order"
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _description = "Purchase Order"
    _order = 'date_order desc, id desc'

    @api.depends('order_line.price_total')
    def _amount_all(self):
        for order in self:
            amount_untaxed = amount_tax = 0.0
            for line in order.order_line:
                amount_untaxed += line.price_subtotal
                # FORWARDPORT UP TO 10.0
                if order.company_id.tax_calculation_rounding_method == 'round_globally':
                    taxes = line.taxes_id.compute_all(line.price_unit, line.order_id.currency_id, line.product_qty, product=line.product_id, partner=line.order_id.partner_id)
                    amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', []))
                else:
                    amount_tax += line.price_tax
            order.update({
                'amount_untaxed': order.currency_id.round(amount_untaxed),
                'amount_tax': order.currency_id.round(amount_tax),
                'amount_total': amount_untaxed + amount_tax,
            })

    @api.depends('order_line.date_planned')
    def _compute_date_planned(self):
        for order in self:
            min_date = False
            for line in order.order_line:
                if not min_date or line.date_planned < min_date:
                    min_date = line.date_planned
            if min_date:
                order.date_planned = min_date

    @api.depends('state', 'order_line.qty_invoiced', 'order_line.product_qty')
    def _get_invoiced(self):
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        for order in self:
            if order.state != 'purchase':
                order.invoice_status = 'no'
                continue

            if any(float_compare(line.qty_invoiced, line.product_qty, precision_digits=precision) == -1 for line in order.order_line):
                order.invoice_status = 'to invoice'
            elif all(float_compare(line.qty_invoiced, line.product_qty, precision_digits=precision) >= 0 for line in order.order_line):
                order.invoice_status = 'invoiced'
            else:
                order.invoice_status = 'no'

    @api.depends('order_line.invoice_lines.invoice_id.state')
    def _compute_invoice(self):
        for order in self:
            invoices = self.env['account.invoice']
            for line in order.order_line:
                invoices |= line.invoice_lines.mapped('invoice_id')
            order.invoice_ids = invoices
            order.invoice_count = len(invoices)

    @api.model
    def _default_picking_type(self):
        type_obj = self.env['stock.picking.type']
        company_id = self.env.context.get('company_id') or self.env.user.company_id.id
        types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)])
        if not types:
            types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id', '=', False)])
        return types[:1]

    @api.depends('order_line.move_ids')
    def _compute_picking(self):
        for order in self:
            pickings = self.env['stock.picking']
            for line in order.order_line:
                # We keep a limited scope on purpose. Ideally, we should also use move_orig_ids and
                # do some recursive search, but that could be prohibitive if not done correctly.
                moves = line.move_ids | line.move_ids.mapped('returned_move_ids')
                moves = moves.filtered(lambda r: r.state != 'cancel')
                pickings |= moves.mapped('picking_id')
            order.picking_ids = pickings
            order.picking_count = len(pickings)

    READONLY_STATES = {
        'purchase': [('readonly', True)],
        'done': [('readonly', True)],
        'cancel': [('readonly', True)],
    }

    name = fields.Char('Order Reference', required=True, index=True, copy=False, default='New')
    origin = fields.Char('Source Document', copy=False,\
        help="Reference of the document that generated this purchase order "
             "request (e.g. a sale order or an internal procurement request)")
    partner_ref = fields.Char('Vendor Reference', copy=False,\
        help="Reference of the sales order or bid sent by the vendor. "
             "It's used to do the matching when you receive the "
             "products as this reference is usually written on the "
             "delivery order sent by your vendor.")
    date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, index=True, copy=False, default=fields.Datetime.now,\
        help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
    date_approve = fields.Date('Approval Date', readonly=1, index=True, copy=False)
    partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
    dest_address_id = fields.Many2one('res.partner', string='Drop Ship Address', states=READONLY_STATES,\
        help="Put an address if you want to deliver directly from the vendor to the customer. "\
             "Otherwise, keep empty to deliver to your own company.")
    currency_id = fields.Many2one('res.currency', 'Currency', required=True, states=READONLY_STATES,\
        default=lambda self: self.env.user.company_id.currency_id.id)
    state = fields.Selection([
        ('draft', 'Draft PO'),
        ('sent', 'RFQ Sent'),
        ('to approve', 'To Approve'),
        ('purchase', 'Purchase Order'),
        ('done', 'Done'),
        ('cancel', 'Cancelled')
        ], string='Status', readonly=True, index=True, copy=False, default='draft', track_visibility='onchange')
    order_line = fields.One2many('purchase.order.line', 'order_id', string='Order Lines', states=READONLY_STATES, copy=True)
    notes = fields.Text('Terms and Conditions')

    invoice_count = fields.Integer(compute="_compute_invoice", string='# of Invoices', copy=False, default=0)
    invoice_ids = fields.Many2many('account.invoice', compute="_compute_invoice", string='Invoices', copy=False)
    invoice_status = fields.Selection([
        ('no', 'Not purchased'),
        ('to invoice', 'Waiting Invoices'),
        ('invoiced', 'Invoice Received'),
        ], string='Invoice Status', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no')

    picking_count = fields.Integer(compute='_compute_picking', string='Receptions', default=0)
    picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False)

    # There is no inverse function on purpose since the date may be different on each line
    date_planned = fields.Datetime(string='Scheduled Date', compute='_compute_date_planned', required=True, index=True, oldname='minimum_planned_date')

    amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
    amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all')
    amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all')

    fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position', oldname='fiscal_position')
    payment_term_id = fields.Many2one('account.payment.term', 'Payment Term')
    incoterm_id = fields.Many2one('stock.incoterms', 'Incoterm', help="International Commercial Terms are a series of predefined commercial terms used in international transactions.")

    product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
    create_uid = fields.Many2one('res.users', 'Responsible')
    company_id = fields.Many2one('res.company', 'Company', required=True, index=True, states=READONLY_STATES, default=lambda self: self.env.user.company_id.id)

    picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
        help="This will determine picking type of incoming shipment")
    default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
        help="Technical field used to display the Drop Ship Address", readonly=True)
    group_id = fields.Many2one('procurement.group', string="Procurement Group", copy=False)

    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100):
        args = args or []
        domain = []
        if name:
            domain = ['|', ('name', operator, name), ('partner_ref', operator, name)]
        pos = self.search(domain + args, limit=limit)
        return pos.name_get()

    @api.multi
    @api.depends('name', 'partner_ref')
    def name_get(self):
        result = []
        for po in self:
            name = po.name
            if po.partner_ref:
                name += ' ('+po.partner_ref+')'
            result.append((po.id, name))
        return result

    @api.model
    def create(self, vals):
        if vals.get('name', 'New') == 'New':
            vals['name'] = self.env['ir.sequence'].next_by_code('purchase.order') or '/'
        return super(PurchaseOrder, self).create(vals)

    @api.multi
    def unlink(self):
        for order in self:
            if not order.state == 'cancel':
                raise UserError(_('In order to delete a purchase order, you must cancel it first.'))
        return super(PurchaseOrder, self).unlink()

    @api.multi
    def copy(self, default=None):
        new_po = super(PurchaseOrder, self).copy(default=default)
        for line in new_po.order_line:
            seller = line.product_id._select_seller(
                line.product_id, partner_id=line.partner_id, quantity=line.product_qty,
                date=line.order_id.date_order and line.order_id.date_order[:10], uom_id=line.product_uom)
            line.date_planned = line._get_date_planned(seller)
        return new_po

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'purchase':
            return 'purchase.mt_rfq_approved'
        elif 'state' in init_values and self.state == 'to approve':
            return 'purchase.mt_rfq_confirmed'
        elif 'state' in init_values and self.state == 'done':
            return 'purchase.mt_rfq_done'
        return super(PurchaseOrder, self)._track_subtype(init_values)

    @api.onchange('partner_id', 'company_id')
    def onchange_partner_id(self):
        if not self.partner_id:
            self.fiscal_position_id = False
            self.payment_term_id = False
            self.currency_id = False
        else:
            self.fiscal_position_id = self.env['account.fiscal.position'].with_context(company_id=self.company_id.id).get_fiscal_position(self.partner_id.id)
            self.payment_term_id = self.partner_id.property_supplier_payment_term_id.id
            self.currency_id = self.partner_id.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id
        return {}

    @api.onchange('picking_type_id')
    def _onchange_picking_type_id(self):
        if self.picking_type_id.default_location_dest_id.usage != 'customer':
            self.dest_address_id = False

    @api.multi
    def action_rfq_send(self):
        '''
        This function opens a window to compose an email, with the edi purchase template message loaded by default
        '''
        self.ensure_one()
        ir_model_data = self.env['ir.model.data']
        try:
            if self.env.context.get('send_rfq', False):
                template_id = ir_model_data.get_object_reference('purchase', 'email_template_edi_purchase')[1]
            else:
                template_id = ir_model_data.get_object_reference('purchase', 'email_template_edi_purchase_done')[1]
        except ValueError:
            template_id = False
        try:
            compose_form_id = ir_model_data.get_object_reference('mail', 'email_compose_message_wizard_form')[1]
        except ValueError:
            compose_form_id = False
        ctx = dict(self.env.context or {})
        ctx.update({
            'default_model': 'purchase.order',
            'default_res_id': self.ids[0],
            'default_use_template': bool(template_id),
            'default_template_id': template_id,
            'default_composition_mode': 'comment',
        })
        return {
            'name': _('Compose Email'),
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'mail.compose.message',
            'views': [(compose_form_id, 'form')],
            'view_id': compose_form_id,
            'target': 'new',
            'context': ctx,
        }

    @api.multi
    def print_quotation(self):
        self.write({'state': "sent"})
        return self.env['report'].get_action(self, 'purchase.report_purchasequotation')

    @api.multi
    def button_approve(self):
        self.write({'state': 'purchase'})
        self._create_picking()
        return {}

    @api.multi
    def button_draft(self):
        self.write({'state': 'draft'})
        return {}

    @api.multi
    def button_confirm(self):
        for order in self:
            if order.state not in ['draft', 'sent']:
                continue
            order._add_supplier_to_product()
            # Deal with double validation process
            if order.company_id.po_double_validation == 'one_step'\
                    or (order.company_id.po_double_validation == 'two_step'\
                        and order.amount_total < self.env.user.company_id.currency_id.compute(order.company_id.po_double_validation_amount, order.currency_id))\
                    or order.user_has_groups('purchase.group_purchase_manager'):
                order.button_approve()
            else:
                order.write({'state': 'to approve'})
        return {}

    @api.multi
    def button_cancel(self):
        for order in self:
            for pick in order.picking_ids:
                if pick.state == 'done':
                    raise UserError(_('Unable to cancel purchase order %s as some receptions have already been done.') % (order.name))
            for inv in order.invoice_ids:
                if inv and inv.state not in ('cancel', 'draft'):
                    raise UserError(_("Unable to cancel this purchase order.i You must first cancel related vendor bills."))

            for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'):
                pick.action_cancel()
            if not self.env.context.get('cancel_procurement'):
                procurements = order.order_line.mapped('procurement_ids')
                procurements.filtered(lambda r: r.state not in ('cancel', 'exception') and r.rule_id.propagate).write({'state': 'cancel'})
                procurements.filtered(lambda r: r.state not in ('cancel', 'exception') and not r.rule_id.propagate).write({'state': 'exception'})
                moves = procurements.filtered(lambda r: r.rule_id.propagate).mapped('move_dest_id')
                moves.filtered(lambda r: r.state != 'cancel').action_cancel()

        self.write({'state': 'cancel'})

    @api.multi
    def button_done(self):
        self.write({'state': 'done'})

    @api.multi
    def _get_destination_location(self):
        self.ensure_one()
        if self.dest_address_id:
            return self.dest_address_id.property_stock_customer.id
        return self.picking_type_id.default_location_dest_id.id

    @api.model
    def _prepare_picking(self):
        if not self.group_id:
            self.group_id = self.group_id.create({
                'name': self.name,
                'partner_id': self.partner_id.id
            })
        if not self.partner_id.property_stock_supplier.id:
            raise UserError(_("You must set a Vendor Location for this partner %s") % self.partner_id.name)
        return {
            'picking_type_id': self.picking_type_id.id,
            'partner_id': self.partner_id.id,
            'date': self.date_order,
            'origin': self.name,
            'location_dest_id': self._get_destination_location(),
            'location_id': self.partner_id.property_stock_supplier.id,
            'company_id': self.company_id.id,
        }

    @api.multi
    def _create_picking(self):
        for order in self:
            if any([ptype in ['product', 'consu'] for ptype in order.order_line.mapped('product_id.type')]):
                res = order._prepare_picking()
                picking = self.env['stock.picking'].create(res)
                moves = order.order_line.filtered(lambda r: r.product_id.type in ['product', 'consu'])._create_stock_moves(picking)
                move_ids = moves.action_confirm()
                moves = self.env['stock.move'].browse(move_ids)
                moves.force_assign()
        return True

    @api.multi
    def _add_supplier_to_product(self):
        # Add the partner in the supplier list of the product if the supplier is not registered for
        # this product. We limit to 10 the number of suppliers for a product to avoid the mess that
        # could be caused for some generic products ("Miscellaneous").
        for line in self.order_line:
            # Do not add a contact as a supplier
            partner = self.partner_id if not self.partner_id.parent_id else self.partner_id.parent_id
            if partner not in line.product_id.seller_ids.mapped('name') and len(line.product_id.seller_ids) <= 10:
                currency = partner.property_purchase_currency_id or self.env.user.company_id.currency_id
                supplierinfo = {
                    'name': partner.id,
                    'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1,
                    'product_uom': line.product_uom.id,
                    'min_qty': 0.0,
                    'price': self.currency_id.compute(line.price_unit, currency),
                    'currency_id': currency.id,
                    'delay': 0,
                }
                vals = {
                    'seller_ids': [(0, 0, supplierinfo)],
                }
                try:
                    line.product_id.write(vals)
                except AccessError:  # no write access rights -> just ignore
                    break

    @api.multi
    def action_view_picking(self):
        '''
        This function returns an action that display existing picking orders of given purchase order ids.
        When only one found, show the picking immediately.
        '''
        action = self.env.ref('stock.action_picking_tree')
        result = action.read()[0]

        #override the context to get rid of the default filtering on picking type
        result.pop('id', None)
        result['context'] = {}
        pick_ids = sum([order.picking_ids.ids for order in self], [])
        #choose the view_mode accordingly
        if len(pick_ids) > 1:
            result['domain'] = "[('id','in',[" + ','.join(map(str, pick_ids)) + "])]"
        elif len(pick_ids) == 1:
            res = self.env.ref('stock.view_picking_form', False)
            result['views'] = [(res and res.id or False, 'form')]
            result['res_id'] = pick_ids and pick_ids[0] or False
        return result

    @api.multi
    def action_view_invoice(self):
        '''
        This function returns an action that display existing vendor bills of given purchase order ids.
        When only one found, show the vendor bill immediately.
        '''
        action = self.env.ref('account.action_invoice_tree2')
        result = action.read()[0]

        #override the context to get rid of the default filtering
        result['context'] = {'type': 'in_invoice', 'default_purchase_id': self.id}

        if not self.invoice_ids:
            # Choose a default account journal in the same currency in case a new invoice is created
            journal_domain = [
                ('type', '=', 'purchase'),
                ('company_id', '=', self.company_id.id),
                ('currency_id', '=', self.currency_id.id),
            ]
            default_journal_id = self.env['account.journal'].search(journal_domain, limit=1)
            if default_journal_id:
                result['context']['default_journal_id'] = default_journal_id.id
        else:
            # Use the same account journal than a previous invoice
            result['context']['default_journal_id'] = self.invoice_ids[0].journal_id.id

        #choose the view_mode accordingly
        if len(self.invoice_ids) != 1:
            result['domain'] = "[('id', 'in', " + str(self.invoice_ids.ids) + ")]"
        elif len(self.invoice_ids) == 1:
            res = self.env.ref('account.invoice_supplier_form', False)
            result['views'] = [(res and res.id or False, 'form')]
            result['res_id'] = self.invoice_ids.id
        return result

    @api.multi
    def action_set_date_planned(self):
        # implementation for 9.0 where PO date_planned is not stored
        date_planned = self.env.context.get('date_planned')
        if not date_planned:
            return
        for order in self:
            for line in order.order_line:
                line.update({'date_planned': date_planned})
class AccountPaymentGroup(models.Model):
    _name = "account.payment.group"
    _description = "Payment Group"
    _order = "payment_date desc"
    _inherit = 'mail.thread'

    # campos copiados de payment
    # payment_type = fields.Selection(
    #     [('outbound', 'Send Money'), ('inbound', 'Receive Money')],
    #     string='Payment Type',
    #     required=True,
    #     readonly=True,
    #     states={'draft': [('readonly', False)]},
    # )
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        required=True,
        index=True,
        default=lambda self: self.env.user.company_id,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    partner_type = fields.Selection(
        [('customer', 'Customer'), ('supplier', 'Vendor')],
        track_visibility='always',
    )
    partner_id = fields.Many2one(
        'res.partner',
        string='Partner',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
        track_visibility='always',
    )
    commercial_partner_id = fields.Many2one(
        related='partner_id.commercial_partner_id',
        readonly=True,
    )
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        required=True,
        default=lambda self: self.env.user.company_id.currency_id,
        readonly=True,
        states={'draft': [('readonly', False)]},
        track_visibility='always',
    )
    payment_date = fields.Date(
        string='Payment Date',
        default=fields.Date.context_today,
        required=True,
        copy=False,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    communication = fields.Char(
        string='Memo',
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    notes = fields.Text(
        string='Notes'
    )

    # campos nuevos
    # reconcile = fields.Selection([
    #     ('invoices', 'Invoices'),
    #     ('move_lines', 'Entry Lines')],
    #     required=True,
    #     default='move_lines',
    #     # default='invoices',
    # )
    # rename fields or labels
    matched_amount = fields.Monetary(
        compute='_compute_matched_amounts',
        currency_field='currency_id',
    )
    unmatched_amount = fields.Monetary(
        compute='_compute_matched_amounts',
        currency_field='currency_id',
    )

    @api.multi
    @api.depends(
        'state',
        'payments_amount',
        'matched_move_line_ids.payment_group_matched_amount')
    def _compute_matched_amounts(self):
        for rec in self:
            if rec.state != 'posted':
                continue
            # damos vuelta signo porque el payments_amount tmb lo da vuelta,
            # en realidad porque siempre es positivo y se define en funcion
            # a si es pago entrante o saliente
            sign = rec.partner_type == 'supplier' and -1.0 or 1.0
            rec.matched_amount = sign * sum(
                rec.matched_move_line_ids.with_context(
                    payment_group_id=rec.id).mapped(
                        'payment_group_matched_amount'))
            rec.unmatched_amount = rec.payments_amount - rec.matched_amount

    selected_finacial_debt = fields.Monetary(
        string='Selected Financial Debt',
        compute='_compute_selected_debt',
    )
    selected_debt = fields.Monetary(
        # string='To Pay lines Amount',
        string='Selected Debt',
        compute='_compute_selected_debt',
    )
    # this field is to be used by others
    selected_debt_untaxed = fields.Monetary(
        # string='To Pay lines Amount',
        string='Selected Debt Untaxed',
        compute='_compute_selected_debt',
    )
    unreconciled_amount = fields.Monetary(
        string='Adjusment / Advance',
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    # reconciled_amount = fields.Monetary(compute='_compute_amounts')
    to_pay_amount = fields.Monetary(
        compute='_compute_to_pay_amount',
        inverse='_inverse_to_pay_amount',
        string='To Pay Amount',
        # string='Total To Pay Amount',
        readonly=True,
        states={'draft': [('readonly', False)]},
        track_visibility='always',
    )

    payments_amount = fields.Monetary(
        compute='_compute_payments_amount',
        string='Amount',
        track_visibility='always',
    )
    # name = fields.Char(
    #     readonly=True,
    #     copy=False,
    #     default="Draft Payment"
    # )   # The name is attributed upon post()
    state = fields.Selection([
        ('draft', 'Draft'),
        ('confirmed', 'Confirmed'),
        ('posted', 'Posted'),
        # ('sent', 'Sent'),
        # ('reconciled', 'Reconciled')
    ], readonly=True, default='draft', copy=False, string="Status",
        track_visibility='onchange',
    )
    move_lines_domain = (
        "["
        "('partner_id.commercial_partner_id', '=', commercial_partner_id),"
        "('account_id.internal_type', '=', account_internal_type),"
        "('account_id.reconcile', '=', True),"
        "('reconciled', '=', False),"
        "('company_id', '=', company_id),"
        # '|',
        # ('amount_residual', '!=', False),
        # ('amount_residual_currency', '!=', False),
        "]")
    debt_move_line_ids = fields.Many2many(
        'account.move.line',
        # por alguna razon el related no funciona bien ni con states ni
        # actualiza bien con el onchange, hacemos computado mejor
        compute='_compute_debt_move_line_ids',
        inverse='_inverse_debt_move_line_ids',
        string="Debt Lines",
        # no podemos ordenar por due date porque esta hardecodeado en
        # funcion _get_pair_to_reconcile
        help="Payment will be automatically matched with the oldest lines of "
        "this list (by date, no by maturity date). You can remove any line you"
        " dont want to be matched.",
        domain=move_lines_domain,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    to_pay_move_line_ids = fields.Many2many(
        'account.move.line',
        'account_move_line_payment_group_to_pay_rel',
        'payment_group_id',
        'to_pay_line_id',
        string="To Pay Lines",
        help='This lines are the ones the user has selected to be paid.',
        copy=False,
        domain=move_lines_domain,
        # lo hacemos readonly por vista y no por aca porque el relatd si no
        # no funcionaba bien
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    matched_move_line_ids = fields.Many2many(
        'account.move.line',
        compute='_compute_matched_move_line_ids',
        help='Lines that has been matched to payments, only available after '
        'payment validation',
    )
    payment_subtype = fields.Char(
        compute='_compute_payment_subtype'
    )
    pop_up = fields.Boolean(
        # campo que agregamos porque el  invisible="context.get('pop_up')"
        # en las pages no se comportaba bien con los attrs
        compute='_compute_payment_pop_up',
        default=lambda x: x._context.get('pop_up', False),
    )

    @api.multi
    @api.depends('to_pay_move_line_ids')
    def _compute_debt_move_line_ids(self):
        for rec in self:
            rec.debt_move_line_ids = rec.to_pay_move_line_ids

    @api.multi
    @api.onchange('debt_move_line_ids')
    def _inverse_debt_move_line_ids(self):
        for rec in self:
            rec.to_pay_move_line_ids = rec.debt_move_line_ids

    @api.multi
    def _compute_payment_pop_up(self):
        pop_up = self._context.get('pop_up', False)
        for rec in self:
            rec.pop_up = pop_up

    @api.multi
    @api.depends('company_id.double_validation', 'partner_type')
    def _compute_payment_subtype(self):
        for rec in self:
            if (rec.partner_type == 'supplier' and
                    rec.company_id.double_validation):
                payment_subtype = 'double_validation'
            else:
                payment_subtype = 'simple'
            rec.payment_subtype = payment_subtype

    @api.one
    def _compute_matched_move_line_ids(self):
        """
        Lar partial reconcile vinculan dos apuntes con credit_move_id y
        debit_move_id.
        Buscamos primeros todas las que tienen en credit_move_id algun apunte
        de los que se genero con un pago, etnonces la contrapartida
        (debit_move_id), son cosas que se pagaron con este pago. Repetimos
        al revz (debit_move_id vs credit_move_id)
        """

        lines = self.move_line_ids.browse()
        # not sure why but self.move_line_ids dont work the same way
        payment_lines = self.payment_ids.mapped('move_line_ids')

        # este metodo deberia ser mas eficiente que el de abajo
        reconciles = self.env['account.partial.reconcile'].search([
            ('credit_move_id', 'in', payment_lines.ids)])
        lines |= reconciles.mapped('debit_move_id')

        reconciles = self.env['account.partial.reconcile'].search([
            ('debit_move_id', 'in', payment_lines.ids)])
        lines |= reconciles.mapped('credit_move_id')

        # otro metodo, tal vez mas claro pero menos eficiente
        # for aml in payment_lines:
        #     if aml.account_id.reconcile:
        #         if aml.credit > 0:
        #             lines |= aml.matched_debit_ids.mapped('debit_move_id')
        #         else:
        #             lines |= aml.matched_credit_ids.mapped('credit_move_id')

        self.matched_move_line_ids = lines - payment_lines

    payment_difference = fields.Monetary(
        compute='_compute_payment_difference',
        # TODO rename field or remove string
        # string='Remaining Residual',
        readonly=True,
        string="Payments Difference",
        help="Difference between selected debt (or to pay amount) and "
        "payments amount"
    )
    # TODO por ahora no implementamos
    # payment_difference_handling = fields.Selection(
    #     [('open', 'Keep open'), ('reconcile', 'Mark invoice as fully paid')],
    #     default='open',
    #     string="Payment Difference",
    #     copy=False
    # )
    # TODO add journal?
    # writeoff_account_id = fields.Many2one(
    #     'account.account',
    #     string="Difference Account",
    #     domain=[('deprecated', '=', False)],
    #     copy=False
    # )
    payment_ids = fields.One2many(
        'account.payment',
        'payment_group_id',
        string='Payment Lines',
        ondelete='cascade',
        copy=False,
        readonly=True,
        states={
            'draft': [('readonly', False)],
            'confirmed': [('readonly', False)]},
    )
    move_line_ids = fields.One2many(
        related='payment_ids.move_line_ids',
        readonly=True,
        copy=False,
    )
    account_internal_type = fields.Char(
        compute='_compute_account_internal_type'
    )

    @api.multi
    @api.depends('partner_type')
    def _compute_account_internal_type(self):
        for rec in self:
            if rec.partner_type:
                rec.account_internal_type = MAP_PARTNER_TYPE_ACCOUNT_TYPE[
                    rec.partner_type]

    @api.multi
    @api.depends('to_pay_amount', 'payments_amount')
    def _compute_payment_difference(self):
        for rec in self:
            # if rec.payment_subtype != 'double_validation':
            #     continue
            rec.payment_difference = rec.to_pay_amount - rec.payments_amount

    @api.multi
    @api.depends('payment_ids.amount_company_currency')
    def _compute_payments_amount(self):
        for rec in self:
            rec.payments_amount = sum(rec.payment_ids.mapped(
                'amount_company_currency'))
            # payments_amount = sum([
            #     x.payment_type == 'inbound' and
            #     x.amount_company_currency or -x.amount_company_currency for
            #     x in rec.payment_ids])
            # rec.payments_amount = (
            #     rec.partner_type == 'supplier' and
            #     -payments_amount or payments_amount)

    # TODO analizar en v10
    # el onchange no funciona bien en o2m, si usamos write se escribe pero no
    # se actualiza en interfaz lo cual puede ser confuzo, por ahora lo
    # comentamos
    # @api.onchange('payment_date')
    # def change_payment_date(self):
    #     # self.payment_ids.write({'payment_date': self.payment_date})
    #     for line in self.payment_ids:
    #         line.payment_date = self.payment_date

    @api.one
    # @api.onchange(
    @api.depends(
        # 'to_pay_move_line_ids',
        'to_pay_move_line_ids.amount_residual',
        'to_pay_move_line_ids.amount_residual_currency',
        'to_pay_move_line_ids.currency_id',
        'to_pay_move_line_ids.invoice_id',
        'payment_date',
        'currency_id',
    )
    # @api.constrains(
    #     'to_pay_move_line_ids',
    #     'payment_date',
    #     'currency_id',
    # )
    # def set_selected_debt(self):
    def _compute_selected_debt(self):
        # # we dont make it computed because we want to store value.
        # # TODO check if odoo implement this kind of hybrid field
        # payment_currency = self.currency_id or self.company_id.currency_id

        # total_untaxed = total = 0
        # for rml in self.to_pay_move_line_ids:
        #     # factor for total_untaxed
        #     invoice = rml.invoice_id
        #     factor = invoice and invoice._get_tax_factor() or 1.0

        #     # si tiene moneda y es distinta convertimos el monto de moneda
        #     # si tiene moneda y es igual llevamos el monto de moneda
        #     # si no tiene moneda y es distinta convertimos el monto comun
        #     # si no tiene moneda y es igual llevamos el monto comun
        #     if rml.currency_id:
        #         if rml.currency_id != payment_currency:
        #             line_amount = rml.currency_id.with_context(
        #                 date=self.payment_date).compute(
        #                 rml.amount_residual_currency, payment_currency)
        #         else:
        #             line_amount = rml.amount_residual_currency
        #     else:
        #         if self.company_id.currency_id != payment_currency:
        #             line_amount = self.company_id.currency_id.with_context(
        #                 date=self.payment_date).compute(
        #                 rml.amount_residual, payment_currency)
        #         else:
        #             line_amount = rml.amount_residual
        #     total += line_amount
        #     total_untaxed += line_amount * factor
        # self.selected_debt = abs(total)
        # self.selected_debt_untaxed = abs(total_untaxed)
        selected_finacial_debt = 0.0
        selected_debt = 0.0
        selected_debt_untaxed = 0.0
        for line in self.to_pay_move_line_ids:
            selected_finacial_debt += line.financial_amount_residual
            selected_debt += line.amount_residual
            # factor for total_untaxed
            invoice = line.invoice_id
            factor = invoice and invoice._get_tax_factor() or 1.0
            selected_debt_untaxed += line.amount_residual * factor
        sign = self.partner_type == 'supplier' and -1.0 or 1.0
        self.selected_finacial_debt = selected_finacial_debt * sign
        self.selected_debt = selected_debt * sign
        self.selected_debt_untaxed = selected_debt_untaxed * sign

    @api.multi
    @api.depends(
        'selected_debt', 'unreconciled_amount')
    def _compute_to_pay_amount(self):
        for rec in self:
            rec.to_pay_amount = rec.selected_debt + rec.unreconciled_amount

    @api.multi
    def _inverse_to_pay_amount(self):
        for rec in self:
            rec.unreconciled_amount = rec.to_pay_amount - rec.selected_debt

    @api.onchange('partner_id', 'partner_type', 'company_id')
    def _refresh_payments_and_move_lines(self):
        # clean actual invoice and payments
        # no hace falta
        if self._context.get('pop_up'):
            return
        # not sure why but state field is false on payments so they can
        # not be unliked, this fix that
        self.invalidate_cache(['payment_ids'])
        self.payment_ids.unlink()
        self.add_all()
        # if self.payment_subtype == 'double_validation':
        #     self._add_all('to_pay_move_line_ids')
        # else:
        #     self._add_all('debt_move_line_ids')
        # if self.to_pay_move_line_ids:
        #     raise UserError('asdasdasd')
        # else:
        #     self.debt_move_line_ids = False
        #     self.payment_ids.unlink()
        #     self.add_all()

    @api.multi
    def add_all(self):
        for rec in self:
            # TODO ver si es necesario agregar un remove o el update las borra
            domain = [
                ('partner_id.commercial_partner_id', '=',
                    rec.commercial_partner_id.id),
                ('account_id.internal_type', '=',
                    rec.account_internal_type),
                ('account_id.reconcile', '=', True),
                ('reconciled', '=', False),
                ('company_id', '=', rec.company_id.id),
                # '|',
                # ('amount_residual', '!=', False),
                # ('amount_residual_currency', '!=', False),
            ]
            rec.to_pay_move_line_ids = rec.env['account.move.line'].search(
                domain)

    @api.multi
    def remove_all(self):
        self.to_pay_move_line_ids = False

    @api.model
    def default_get(self, fields):
        # TODO si usamos los move lines esto no haria falta
        rec = super(AccountPaymentGroup, self).default_get(fields)
        to_pay_move_line_ids = self._context.get('to_pay_move_line_ids')
        to_pay_move_lines = self.env['account.move.line'].browse(
            to_pay_move_line_ids).filtered(lambda x: (
                x.account_id.reconcile and
                x.account_id.internal_type in ('receivable', 'payable')))
        if to_pay_move_lines:
            partner = to_pay_move_lines.mapped('partner_id')
            if len(partner) != 1:
                raise ValidationError(_(
                    'You can not send to pay lines from different partners'))

            internal_type = to_pay_move_lines.mapped(
                'account_id.internal_type')
            if len(internal_type) != 1:
                raise ValidationError(_(
                    'You can not send to pay lines from different partners'))
            rec['partner_id'] = partner[0].id
            rec['partner_type'] = MAP_ACCOUNT_TYPE_PARTNER_TYPE[
                internal_type[0]]
            # rec['currency_id'] = invoice['currency_id'][0]
            # rec['payment_type'] = (
            #     internal_type[0] == 'receivable' and
            #     'inbound' or 'outbound')
            rec['to_pay_move_line_ids'] = [(6, False, to_pay_move_line_ids)]
        return rec
        # print 'rec', rec
        # invoice_defaults = self.resolve_2many_commands(
        #     'invoice_ids', rec.get('invoice_ids'))
        # print 'aaaaaa'
        # print 'aaaaaa', self._context
        # print 'aaaaaa', invoice_defaults
        # print 'aaaaaa', invoice_defaults
        # if invoice_defaults and len(invoice_defaults) == 1:
        #     invoice = invoice_defaults[0]
        #     rec['communication'] = invoice[
        #         'reference'] or invoice['name'] or invoice['number']
        #     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']
        # print 'rec', rec

    @api.multi
    def button_journal_entries(self):
        return {
            'name': _('Journal Items'),
            'view_type': 'form',
            'view_mode': 'tree,form',
            'res_model': 'account.move.line',
            'view_id': False,
            'type': 'ir.actions.act_window',
            'domain': [('payment_id', 'in', self.payment_ids.ids)],
        }

    @api.multi
    def unreconcile(self):
        for rec in self:
            rec.payment_ids.unreconcile()
            # TODO en alguos casos setear sent como en payment?
            rec.write({'state': 'posted'})

    @api.multi
    def cancel(self):
        for rec in self:
            # because child payments dont have invoices we remove reconcile
            for move in rec.move_line_ids.mapped('move_id'):
                rec.matched_move_line_ids.remove_move_reconcile()
                # TODO borrar esto si con el de arriba va bien
                # if rec.to_pay_move_line_ids:
                #     move.line_ids.remove_move_reconcile()
            rec.payment_ids.cancel()
            rec.state = 'draft'

    @api.multi
    def unlink(self):
        if any(rec.state != 'draft' for rec in self):
            raise ValidationError(_(
                "You can not delete a payment that is already posted"))
        return super(AccountPaymentGroup, self).unlink()

    @api.multi
    def confirm(self):
        for rec in self:
            accounts = rec.to_pay_move_line_ids.mapped('account_id')
            if len(accounts) > 1:
                raise ValidationError(_(
                    'To Pay Lines must be of the same account!'))
            rec.state = 'confirmed'

    @api.multi
    def post(self):
        # dont know yet why, but if we came from an invoice context values
        # break behaviour, for eg. with demo user error writing account.account
        # and with other users, error with block date of accounting
        # TODO we should look for a better way to solve this
        self = self.with_context({})
        for rec in self:
            # TODO if we want to allow writeoff then we can disable this
            # constrain and send writeoff_journal_id and writeoff_acc_id
            if not rec.payment_ids:
                raise ValidationError(_(
                    'You can not confirm a payment group without payment '
                    'lines!'))
            if (rec.payment_subtype == 'double_validation' and
                    rec.payment_difference):
                raise ValidationError(_(
                    'To Pay Amount and Payment Amount must be equal!'))

            writeoff_acc_id = False
            writeoff_journal_id = False

            rec.payment_ids.post()
            counterpart_aml = rec.payment_ids.mapped('move_line_ids').filtered(
                lambda r: not r.reconciled and r.account_id.internal_type in (
                    'payable', 'receivable'))
            (counterpart_aml + (rec.to_pay_move_line_ids)).reconcile(
                writeoff_acc_id, writeoff_journal_id)
            rec.state = 'posted'
Beispiel #11
0
class SaleOrderGrantExpond(models.Model):
    _inherit = 'sale.order'

    amount_grant_number = fields.Integer(string='总期数', readonly=True)
    amount_grant_already_number = fields.Integer(string='已出期数', readonly=True)
    amount_grant_surplus_next = fields.Monetary(string='下期发放', readonly=True)
    amount_grant_surplus_all = fields.Monetary(string='剩余发放', readonly=True)
    amount_grant_rate = fields.Float(string=u'税点',
                                     requird=True,
                                     digits=(16, 3),
                                     readonly=True)
    amount_grant_confirm = fields.Char(
        string='下期发放情况',
        help='这是本期补助发放情况,双方全部确认后,即认为本次客户这正常获得补助',
        readonly=True)
    amount_grant_confirm_a = fields.Selection([('yes', '已确认'), ('no', '未确认')],
                                              readonly=True)
    amount_grant_confirm_b = fields.Selection([('yes', '已确认'), ('no', '未确认')],
                                              readonly=True)
    amount_grant_state = fields.Selection([('draft', '待确认'), ('in', '发放中'),
                                           ('done', '已完毕'), ('other', '退费')],
                                          string='补贴进度',
                                          readonly=True)
    amount_grant_state_backup = fields.Selection([('draft', '待确认'),
                                                  ('in', '发放中'),
                                                  ('done', '已完毕'),
                                                  ('other', '退费')],
                                                 string='补贴进度',
                                                 readonly=True,
                                                 help='用作在退款时备份补贴进度字段,可做撤销用')

    # 清空补贴金额以后,清空补贴相关字段
    @api.constrains('amount_grant', 'amount_grant_number')
    @api.onchange('amount_grant', 'amount_grant_number')
    def check_amount_grant(self):
        self.update({
            'amount_grant_already_number': 0,
            'amount_grant_surplus_next': 0.0,
            'amount_grant_confirm': False,
            'amount_grant_state': False,
        })
        self.partner_id.write({'sale_order_grant_message': False})
        if self.amount_grant == 0.0:
            self.update({
                'amount_grant_rate': 5.0,
                'amount_grant_surplus_all': 0.0,
                'amount_grant_confirm_a': False,
                'amount_grant_confirm_b': False,
            })
        elif self.amount_grant > 0.0:
            rate = self.amount_grant_rate if (
                self.amount_grant_rate != 0.0) else 5.0
            self.update({
                'amount_grant_rate':
                5.0,
                'amount_grant_surplus_all':
                self.amount_grant - self.amount_grant * (rate / 100.0),
                'amount_grant_confirm_a':
                'no',
                'amount_grant_confirm_b':
                'no',
            })

    @api.onchange('amount_grant_number')
    def _check_amount_grant_number(self):
        if self.amount_grant_number > 0.0:
            self.check_amount_grant()
            self.amount_grant_surplus_all = self.amount_grant - self.amount_grant * (
                self.amount_grant_rate / 100.0)

    # 重载'确认销售'按钮,使点击时客户的补助状态随补助数值显示为'无'或者'带确认'
    @api.one
    def action_confirm(self):
        self.write({
            'amount_grant_confirm_a': 'no',
            'amount_grant_confirm_b': 'no'
        })
        if self.amount_grant == 0.0:
            self.partner_id.write({'sale_grant_state': 'none'})
        elif self.amount_grant > 0.0:
            self.amount_grant_surplus_all = self.amount_grant - self.amount_grant * (
                self.amount_grant_rate / 100.0)
            self.amount_grant_state = 'draft'
            self.partner_id.write({'sale_grant_state': 'draft'})
        return super(SaleOrderGrantExpond, self).action_confirm()

    # 重载'取消'按钮,使点击时客户的补助状态变为none,因为无论何种情况下,报价单状态这个字段始终为'无'
    # 取消当前订单时,还需要判断该用户是否有其他补贴在流程中,若存在,则不重置客户状态为'无'
    @api.multi
    def action_cancel(self):
        self.update({
            'amount_grant_state': False,
        })
        orders = self.env['sale.order'].search([
            ('id', '!=', self.id), ('partner_id', '=', self.partner_id.id),
            ('amount_grant_state', 'in', ('draft', 'in', 'done'))
        ])
        if len(orders) == 0:
            self.partner_id.write({
                'sale_grant_state': 'none',
                'grant_change_sale_number_wizard': False,
            })
            self.check_amount_grant()

        return super(SaleOrderGrantExpond, self).action_cancel()

    # 当退费发起,先查询该客户是否有正常的订单,有则仅将本订单补贴状态异常,无则同时将客户补贴状态异常
    @api.multi
    def state_apply(self):
        self.amount_grant_state_backup = self.amount_grant_state
        self.write({'amount_grant_state': 'other'})
        orders = self.env['sale.order'].search([
            ('id', '!=', self.id), ('partner_id', '=', self.partner_id.id),
            ('amount_grant_state', 'in', ('draft', 'in', 'done'))
        ])
        if len(orders) == 0:
            self.partner_id.write({'sale_grant_state': 'other'})
        return super(SaleOrderGrantExpond, self).state_apply()

    # 当退费发起,先查询该客户是否有正常的订单,有则仅将本订单补贴状态异常,无则同时将客户补贴状态异常
    @api.multi
    def state_sale_to(self):
        self.amount_grant_state = self.amount_grant_state_backup
        orders = self.env['sale.order'].search([
            ('id', '!=', self.id), ('partner_id', '=', self.partner_id.id),
            ('amount_grant_state', 'in', ('draft', 'in', 'done'))
        ])
        if len(orders) == 0:
            self.partner_id.write(
                {'sale_grant_state': self.amount_grant_state})
        return super(SaleOrderGrantExpond, self).state_sale_to()

    # 财务与班主任每一期点击确认,双方确认后,一期通过,直到最后一期完成
    @api.multi
    def _change_number_state(self):
        if self.amount_grant_confirm_a and (self.amount_grant_already_number <
                                            self.amount_grant_number):
            self.amount_grant_already_number += 1
            routine = self.amount_grant / self.amount_grant_number
            last = routine - (self.amount_grant *
                              (self.amount_grant_rate / 100.0))
            timestamp = datetime.datetime.strftime(
                datetime.datetime.now(), DEFAULT_SERVER_DATETIME_FORMAT)

            # 确认全部已完成,订单'补贴进度'、客户'补贴进度'为完成,同时清空下次补贴和剩余补贴
            if self.amount_grant_already_number == self.amount_grant_number:
                self.update({
                    'amount_grant_state':
                    'done',
                    'amount_grant_surplus_next':
                    0.0,
                    'amount_grant_surplus_all':
                    0.0,
                    'amount_grant_confirm':
                    '全' + str(self.amount_grant_already_number) + '/' +
                    str(self.amount_grant_number) + '期▶全部完成于' + timestamp,
                })
                self.partner_id.write({
                    'sale_grant_state':
                    'done',
                    'sale_order_grant_message':
                    self.name + ':' + self.amount_grant_confirm,
                })

            # 未完成最后一期,每期就会计算字段
            if self.amount_grant_already_number < self.amount_grant_number:
                if self.amount_grant_number == (
                        self.amount_grant_already_number + 1):
                    self.amount_grant_surplus_next = last
                    self.amount_grant_surplus_all = last
                else:
                    self.amount_grant_surplus_next = routine
                    self.amount_grant_surplus_all = self.amount_grant_surplus_all - self.amount_grant_surplus_next

                self.update({
                    'amount_grant_state':
                    'in',
                    'amount_grant_confirm_a':
                    'no',
                    'amount_grant_confirm_b':
                    'no',
                    'amount_grant_confirm':
                    '第' + str(self.amount_grant_already_number + 1) + '/' +
                    str(self.amount_grant_number) + '期▶' +
                    str(self.amount_grant_surplus_next) + '¥▶上期' + timestamp +
                    '完成',
                })
                self.partner_id.sale_order_grant_message = self.name + ':' + self.amount_grant_confirm

    # 财务按钮动作
    @api.multi
    def change_already_number_by_a(self):
        self.amount_grant_confirm_a = 'yes'
        if self.amount_grant_confirm_a == self.amount_grant_confirm_b == 'yes':
            self._change_number_state()
        else:
            timestamp = datetime.datetime.strftime(
                datetime.datetime.now(), DEFAULT_SERVER_DATETIME_FORMAT)
            self.amount_grant_confirm = '第' + str(
                self.amount_grant_already_number +
                1) + '/' + str(self.amount_grant_number) + '期▶' + str(
                    self.amount_grant_surplus_next) + '¥▶财务' + timestamp + '确认'
            self.partner_id.write({
                'sale_order_grant_message':
                self.name + ':' + self.amount_grant_confirm
            })

    #班主任按钮动作
    @api.multi
    def change_already_number_by_b(self):
        self.amount_grant_confirm_b = 'yes'
        if self.amount_grant_confirm_a == self.amount_grant_confirm_b == 'yes':
            self._change_number_state()
        else:
            timestamp = datetime.datetime.strftime(
                datetime.datetime.now(), DEFAULT_SERVER_DATETIME_FORMAT)
            self.amount_grant_confirm = '第' + str(
                self.amount_grant_already_number + 1
            ) + '/' + str(self.amount_grant_number) + '期▶' + str(
                self.amount_grant_surplus_next) + '¥▶班主任' + timestamp + '确认'
            self.partner_id.write({
                'sale_order_grant_message':
                self.name + ':' + self.amount_grant_confirm
            })

        #生成草稿付款单
        user = self.env.user.employee_ids
        pay_type = self.env['expense.type'].search([('name', '=', '学员补贴款')])
        ticket_type = self.env['invoice.type'].search([('name', '=', '内部票据')])
        did = False
        dmid = False
        try:
            for employee in self.user_id.employee_ids:
                department = employee.department_id
            did = department.id
        except:
            did = False

        try:
            for employee in self.user_id.employee_ids:
                department = employee.department_id
            dmid = department.manager_id.id
        except:
            dmid = False
        vals = {
            'leixing_sel':
            'c',
            'leixing':
            self.env['email.type'].search([('name', '=', '学员生活补助费')]).id,
            'name':
            self.env.user.id,
            'department':
            did,
            'center':
            self.env.user.company_id.id,
            'chinese_name':
            user.name,
            'pay_danwei':
            self.partner_id.name,
            'pay_mode':
            'zhuangzhang',
            'department_manager':
            dmid,
            'pay_date':
            datetime.date.today(),
            'kaihu':
            self.bankaddress,
            'zhanghu':
            self.banknumber,
            'pay_ids': [((0, 0, {
                'pay_type': pay_type.id,
                'pay_money': self.amount_grant_surplus_next,
                'whether_ticket': 'Yes',
                'ticket_type': ticket_type.id,
                'pay_reason': '学员生活补贴',
            }))]
        }
        pay_account = self.env['pay.account'].create(vals)
Beispiel #12
0
class SaleOrderLine(models.Model):
    _name = 'sale.order.line'
    _description = 'Sales Order Line'
    _order = 'order_id, layout_category_id, sequence, id'

    @api.depends('state', 'product_uom_qty', 'qty_delivered', 'qty_to_invoice', 'qty_invoiced')
    def _compute_invoice_status(self):
        """
        Compute the invoice status of a SO line. Possible statuses:
        - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
          invoice. This is also hte default value if the conditions of no other status is met.
        - to invoice: we refer to the quantity to invoice of the line. Refer to method
          `_get_to_invoice_qty()` for more information on how this quantity is calculated.
        - upselling: this is possible only for a product invoiced on ordered quantities for which
          we delivered more than expected. The could arise if, for example, a project took more
          time than expected but we decided not to invoice the extra cost to the client. This
          occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity
          is removed from the list.
        - invoiced: the quantity invoiced is larger or equal to the quantity ordered.
        """
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        for line in self:
            if line.state not in ('sale', 'done'):
                line.invoice_status = 'no'
            elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
                line.invoice_status = 'to invoice'
            elif line.state == 'sale' and line.product_id.invoice_policy == 'order' and\
                    float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1:
                line.invoice_status = 'upselling'
            elif float_compare(line.qty_invoiced, line.product_uom_qty, precision_digits=precision) >= 0:
                line.invoice_status = 'invoiced'
            else:
                line.invoice_status = 'no'

    @api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_id')
    def _compute_amount(self):
        """
        Compute the amounts of the SO line.
        """
        for line in self:
            price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
            taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_id)
            line.update({
                'price_tax': taxes['total_included'] - taxes['total_excluded'],
                'price_total': taxes['total_included'],
                'price_subtotal': taxes['total_excluded'],
            })

    @api.depends('product_id.invoice_policy', 'order_id.state')
    def _compute_qty_delivered_updateable(self):
        for line in self:
            line.qty_delivered_updateable = line.product_id.invoice_policy in ('order', 'delivery') and line.order_id.state == 'sale' and line.product_id.track_service == 'manual'

    @api.depends('qty_invoiced', 'qty_delivered', 'product_uom_qty', 'order_id.state')
    def _get_to_invoice_qty(self):
        """
        Compute the quantity to invoice. If the invoice policy is order, the quantity to invoice is
        calculated from the ordered quantity. Otherwise, the quantity delivered is used.
        """
        for line in self:
            if line.order_id.state in ['sale', 'done']:
                if line.product_id.invoice_policy == 'order':
                    line.qty_to_invoice = line.product_uom_qty - line.qty_invoiced
                else:
                    line.qty_to_invoice = line.qty_delivered - line.qty_invoiced
            else:
                line.qty_to_invoice = 0

    @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity')
    def _get_invoice_qty(self):
        """
        Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. Note
        that this is the case only if the refund is generated from the SO and that is intentional: if
        a refund made would automatically decrease the invoiced quantity, then there is a risk of reinvoicing
        it automatically, which may not be wanted at all. That's why the refund has to be created from the SO
        """
        for line in self:
            qty_invoiced = 0.0
            for invoice_line in line.invoice_lines:
                if invoice_line.invoice_id.state != 'cancel':
                    if invoice_line.invoice_id.type == 'out_invoice':
                        qty_invoiced += invoice_line.quantity
                    elif invoice_line.invoice_id.type == 'out_refund':
                        qty_invoiced -= invoice_line.quantity
            line.qty_invoiced = qty_invoiced

    @api.depends('price_subtotal', 'product_uom_qty')
    def _get_price_reduce(self):
        for line in self:
            line.price_reduce = line.price_subtotal / line.product_uom_qty if line.product_uom_qty else 0.0

    @api.multi
    def _compute_tax_id(self):
        for line in self:
            fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id
            if fpos:
                # The superuser is used by website_sale in order to create a sale order. We need to make
                # sure we only select the taxes related to the company of the partner. This should only
                # apply if the partner is linked to a company.
                if self.env.uid == SUPERUSER_ID and line.order_id.company_id:
                    taxes = fpos.map_tax(line.product_id.taxes_id).filtered(lambda r: r.company_id == line.order_id.company_id)
                else:
                    taxes = fpos.map_tax(line.product_id.taxes_id)
                line.tax_id = taxes
            else:
                line.tax_id = line.product_id.taxes_id if line.product_id.taxes_id else False

    @api.multi
    def _prepare_order_line_procurement(self, group_id=False):
        self.ensure_one()
        return {
            'name': self.name,
            'origin': self.order_id.name,
            'date_planned': datetime.strptime(self.order_id.date_order, DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(days=self.customer_lead),
            'product_id': self.product_id.id,
            'product_qty': self.product_uom_qty,
            'product_uom': self.product_uom.id,
            'company_id': self.order_id.company_id.id,
            'group_id': group_id,
            'sale_line_id': self.id
        }

    @api.multi
    def _action_procurement_create(self):
        """
        Create procurements based on quantity ordered. If the quantity is increased, new
        procurements are created. If the quantity is decreased, no automated action is taken.
        """
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        new_procs = self.env['procurement.order'] #Empty recordset
        for line in self:
            if line.state != 'sale' or not line.product_id._need_procurement():
                continue
            qty = 0.0
            for proc in line.procurement_ids:
                qty += proc.product_qty
            if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0:
                continue

            if not line.order_id.procurement_group_id:
                vals = line.order_id._prepare_procurement_group()
                line.order_id.procurement_group_id = self.env["procurement.group"].create(vals)

            vals = line._prepare_order_line_procurement(group_id=line.order_id.procurement_group_id.id)
            vals['product_qty'] = line.product_uom_qty - qty
            new_proc = self.env["procurement.order"].create(vals)
            new_procs += new_proc
        new_procs.run()
        return new_procs

    @api.model
    def _get_analytic_invoice_policy(self):
        return ['cost']

    @api.model
    def _get_analytic_track_service(self):
        return []

    @api.model
    def create(self, values):
        onchange_fields = ['name', 'price_unit', 'product_uom', 'tax_id']
        if values.get('order_id') and values.get('product_id') and any(f not in values for f in onchange_fields):
            line = self.new(values)
            line.product_id_change()
            for field in onchange_fields:
                if field not in values:
                    values[field] = line._fields[field].convert_to_write(line[field], line)
        line = super(SaleOrderLine, self).create(values)
        if line.state == 'sale':
            if (not line.order_id.project_id and
                (line.product_id.track_service in self._get_analytic_track_service() or
                 line.product_id.invoice_policy in self._get_analytic_invoice_policy())):
                line.order_id._create_analytic_account()
            line._action_procurement_create()

        return line

    @api.multi
    def write(self, values):
        lines = False
        if 'product_uom_qty' in values:
            precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
            lines = self.filtered(
                lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
        result = super(SaleOrderLine, self).write(values)
        if lines:
            lines._action_procurement_create()
        return result

    order_id = fields.Many2one('sale.order', string='Order Reference', required=True, ondelete='cascade', index=True, copy=False)
    name = fields.Text(string='Description', required=True)
    sequence = fields.Integer(string='Sequence', default=10)

    invoice_lines = fields.Many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_line_id', string='Invoice Lines', copy=False)
    invoice_status = fields.Selection([
        ('upselling', 'Upselling Opportunity'),
        ('invoiced', 'Fully Invoiced'),
        ('to invoice', 'To Invoice'),
        ('no', 'Nothing to Invoice')
        ], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no')
    price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0)

    price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', readonly=True, store=True)
    price_tax = fields.Monetary(compute='_compute_amount', string='Taxes', readonly=True, store=True)
    price_total = fields.Monetary(compute='_compute_amount', string='Total', readonly=True, store=True)

    price_reduce = fields.Monetary(compute='_get_price_reduce', string='Price Reduce', readonly=True, store=True)
    tax_id = fields.Many2many('account.tax', string='Taxes')

    discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)

    product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], change_default=True, ondelete='restrict', required=True)
    product_uom_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
    product_uom = fields.Many2one('product.uom', string='Unit of Measure', required=True)

    qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True)
    qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0)
    qty_to_invoice = fields.Float(
        compute='_get_to_invoice_qty', string='To Invoice', store=True, readonly=True,
        digits=dp.get_precision('Product Unit of Measure'), default=0.0)
    qty_invoiced = fields.Float(
        compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
        digits=dp.get_precision('Product Unit of Measure'), default=0.0)

    salesman_id = fields.Many2one(related='order_id.user_id', store=True, string='Salesperson', readonly=True)
    currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True)
    company_id = fields.Many2one(related='order_id.company_id', string='Company', store=True, readonly=True)
    order_partner_id = fields.Many2one(related='order_id.partner_id', store=True, string='Customer')

    state = fields.Selection([
        ('draft', 'Quotation'),
        ('sent', 'Quotation Sent'),
        ('sale', 'Sale Order'),
        ('done', 'Done'),
        ('cancel', 'Cancelled'),
    ], related='order_id.state', string='Order Status', readonly=True, copy=False, store=True, default='draft')

    customer_lead = fields.Float(
        'Delivery Lead Time', required=True, default=0.0,
        help="Number of days between the order confirmation and the shipping of the products to the customer", oldname="delay")
    procurement_ids = fields.One2many('procurement.order', 'sale_line_id', string='Procurements')

    layout_category_id = fields.Many2one('sale.layout_category', string='Section')
    layout_category_sequence = fields.Integer(related='layout_category_id.sequence', string='Layout Sequence', store=True)
    #  Store is intentionally set in order to keep the "historic" order.

    @api.multi
    def _prepare_invoice_line(self, qty):
        """
        Prepare the dict of values to create the new invoice line for a sales order line.

        :param qty: float quantity to invoice
        """
        self.ensure_one()
        res = {}
        account = self.product_id.property_account_income_id or self.product_id.categ_id.property_account_income_categ_id
        if not account:
            raise UserError(_('Please define income account for this product: "%s" (id:%d) - or for its category: "%s".') % \
                            (self.product_id.name, self.product_id.id, self.product_id.categ_id.name))

        fpos = self.order_id.fiscal_position_id or self.order_id.partner_id.property_account_position_id
        if fpos:
            account = fpos.map_account(account)

        res = {
            'name': self.name,
            'sequence': self.sequence,
            'origin': self.order_id.name,
            'account_id': account.id,
            'price_unit': self.price_unit,
            'quantity': qty,
            'discount': self.discount,
            'uom_id': self.product_uom.id,
            'product_id': self.product_id.id or False,
            'layout_category_id': self.layout_category_id and self.layout_category_id.id or False,
            'product_id': self.product_id.id or False,
            'invoice_line_tax_ids': [(6, 0, self.tax_id.ids)],
            'account_analytic_id': self.order_id.project_id.id,
        }
        return res

    @api.multi
    def invoice_line_create(self, invoice_id, qty):
        """
        Create an invoice line. The quantity to invoice can be positive (invoice) or negative
        (refund).

        :param invoice_id: integer
        :param qty: float quantity to invoice
        """
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        for line in self:
            if not float_is_zero(qty, precision_digits=precision):
                vals = line._prepare_invoice_line(qty=qty)
                vals.update({'invoice_id': invoice_id, 'sale_line_ids': [(6, 0, [line.id])]})
                self.env['account.invoice.line'].create(vals)

    @api.multi
    @api.onchange('product_id')
    def product_id_change(self):
        if not self.product_id:
            return {'domain': {'product_uom': []}}

        vals = {}
        domain = {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
        if not self.product_uom or (self.product_id.uom_id.category_id.id != self.product_uom.category_id.id):
            vals['product_uom'] = self.product_id.uom_id

        product = self.product_id.with_context(
            lang=self.order_id.partner_id.lang,
            partner=self.order_id.partner_id.id,
            quantity=self.product_uom_qty,
            date=self.order_id.date_order,
            pricelist=self.order_id.pricelist_id.id,
            uom=self.product_uom.id
        )

        name = product.name_get()[0][1]
        if product.description_sale:
            name += '\n' + product.description_sale
        vals['name'] = name

        self._compute_tax_id()

        if self.order_id.pricelist_id and self.order_id.partner_id:
            vals['price_unit'] = self.env['account.tax']._fix_tax_included_price(product.price, product.taxes_id, self.tax_id)
        self.update(vals)

        title = False
        message = False
        warning = {}
        if product.sale_line_warn != 'no-message':
            title = _("Warning for %s") % product.name
            message = product.sale_line_warn_msg
            warning['title'] = title
            warning['message'] = message
            if product.sale_line_warn == 'block':
                self.product_id = False
            return {'warning': warning}
        return {'domain': domain}

    @api.onchange('product_uom', 'product_uom_qty')
    def product_uom_change(self):
        if not self.product_uom:
            self.price_unit = 0.0
            return
        if self.order_id.pricelist_id and self.order_id.partner_id:
            product = self.product_id.with_context(
                lang=self.order_id.partner_id.lang,
                partner=self.order_id.partner_id.id,
                quantity=self.product_uom_qty,
                date_order=self.order_id.date_order,
                pricelist=self.order_id.pricelist_id.id,
                uom=self.product_uom.id,
                fiscal_position=self.env.context.get('fiscal_position')
            )
            self.price_unit = self.env['account.tax']._fix_tax_included_price(product.price, product.taxes_id, self.tax_id)

    @api.multi
    def unlink(self):
        if self.filtered(lambda x: x.state in ('sale', 'done')):
            raise UserError(_('You can not remove a sale order line.\nDiscard changes and try setting the quantity to 0.'))
        return super(SaleOrderLine, self).unlink()

    @api.multi
    def _get_delivered_qty(self):
        '''
        Intended to be overridden in sale_stock and sale_mrp
        :return: the quantity delivered
        :rtype: float
        '''
        return 0.0
Beispiel #13
0
class SaleOrder(models.Model):
    _name = "sale.order"
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _description = "Sales Order"
    _order = 'date_order desc, id desc'

    @api.depends('order_line.price_total')
    def _amount_all(self):
        """
        Compute the total amounts of the SO.
        """
        for order in self:
            amount_untaxed = amount_tax = 0.0
            for line in order.order_line:
                amount_untaxed += line.price_subtotal
                amount_tax += line.price_tax
            order.update({
                'amount_untaxed': order.pricelist_id.currency_id.round(amount_untaxed),
                'amount_tax': order.pricelist_id.currency_id.round(amount_tax),
                'amount_total': amount_untaxed + amount_tax,
            })

    @api.depends('state', 'order_line.invoice_status')
    def _get_invoiced(self):
        """
        Compute the invoice status of a SO. Possible statuses:
        - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
          invoice. This is also hte default value if the conditions of no other status is met.
        - to invoice: if any SO line is 'to invoice', the whole SO is 'to invoice'
        - invoiced: if all SO lines are invoiced, the SO is invoiced.
        - upselling: if all SO lines are invoiced or upselling, the status is upselling.

        The invoice_ids are obtained thanks to the invoice lines of the SO lines, and we also search
        for possible refunds created directly from existing invoices. This is necessary since such a
        refund is not directly linked to the SO.
        """
        for order in self:
            invoice_ids = order.order_line.mapped('invoice_lines').mapped('invoice_id')
            # Search for refunds as well
            refund_ids = self.env['account.invoice'].browse()
            if invoice_ids:
                refund_ids = refund_ids.search([('type', '=', 'out_refund'), ('origin', 'in', invoice_ids.mapped('number')), ('origin', '!=', False)])

            line_invoice_status = [line.invoice_status for line in order.order_line]

            if order.state not in ('sale', 'done'):
                invoice_status = 'no'
            elif any(invoice_status == 'to invoice' for invoice_status in line_invoice_status):
                invoice_status = 'to invoice'
            elif all(invoice_status == 'invoiced' for invoice_status in line_invoice_status):
                invoice_status = 'invoiced'
            elif all(invoice_status in ['invoiced', 'upselling'] for invoice_status in line_invoice_status):
                invoice_status = 'upselling'
            else:
                invoice_status = 'no'

            order.update({
                'invoice_count': len(set(invoice_ids.ids + refund_ids.ids)),
                'invoice_ids': invoice_ids.ids + refund_ids.ids,
                'invoice_status': invoice_status
            })

    @api.model
    def _default_note(self):
        return self.env.user.company_id.sale_note

    @api.model
    def _get_default_team(self):
        return self.env['crm.team']._get_default_team_id()

    @api.onchange('fiscal_position_id')
    def _compute_tax_id(self):
        """
        Trigger the recompute of the taxes if the fiscal position is changed on the SO.
        """
        for order in self:
            order.order_line._compute_tax_id()

    name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, index=True, default=lambda self: _('New'))
    origin = fields.Char(string='Source Document', help="Reference of the document that generated this sales order request.")
    client_order_ref = fields.Char(string='Customer Reference', copy=False)

    state = fields.Selection([
        ('draft', 'Quotation'),
        ('sent', 'Quotation Sent'),
        ('sale', 'Sale Order'),
        ('done', 'Done'),
        ('cancel', 'Cancelled'),
        ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft')
    date_order = fields.Datetime(string='Order Date', required=True, readonly=True, index=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, copy=False, default=fields.Datetime.now)
    validity_date = fields.Date(string='Expiration Date', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
        help="Manually set the expiration date of your quotation (offer), or it will set the date automatically based on the template if online quotation is installed.")
    create_date = fields.Datetime(string='Creation Date', readonly=True, index=True, help="Date on which sales order is created.")
    confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the sale order is confirmed.", oldname="date_confirm")
    user_id = fields.Many2one('res.users', string='Salesperson', index=True, track_visibility='onchange', default=lambda self: self.env.user)
    partner_id = fields.Many2one('res.partner', string='Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, index=True, track_visibility='always')
    partner_invoice_id = fields.Many2one('res.partner', string='Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Invoice address for current sales order.")
    partner_shipping_id = fields.Many2one('res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Delivery address for current sales order.")

    pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current sales order.")
    currency_id = fields.Many2one("res.currency", related='pricelist_id.currency_id', string="Currency", readonly=True, required=True)
    project_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a sales order.", copy=False)

    order_line = fields.One2many('sale.order.line', 'order_id', string='Order Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True)

    invoice_count = fields.Integer(string='# of Invoices', compute='_get_invoiced', readonly=True)
    invoice_ids = fields.Many2many("account.invoice", string='Invoices', compute="_get_invoiced", readonly=True, copy=False)
    invoice_status = fields.Selection([
        ('upselling', 'Upselling Opportunity'),
        ('invoiced', 'Fully Invoiced'),
        ('to invoice', 'To Invoice'),
        ('no', 'Nothing to Invoice')
        ], string='Invoice Status', compute='_get_invoiced', store=True, readonly=True, default='no')

    note = fields.Text('Terms and conditions', default=_default_note)

    amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
    amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all', track_visibility='always')
    amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all', track_visibility='always')

    payment_term_id = fields.Many2one('account.payment.term', string='Payment Term', oldname='payment_term')
    fiscal_position_id = fields.Many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position')
    company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('sale.order'))
    team_id = fields.Many2one('crm.team', 'Sales Team', change_default=True, default=_get_default_team, oldname='section_id')
    procurement_group_id = fields.Many2one('procurement.group', 'Procurement Group', copy=False)

    product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')

    @api.model
    def _get_customer_lead(self, product_tmpl_id):
        return False

    @api.multi
    def button_dummy(self):
        return True

    @api.multi
    def unlink(self):
        for order in self:
            if order.state not in ('draft', 'cancel'):
                raise UserError(_('You can not delete a sent quotation or a sales order! Try to cancel it before.'))
        return super(SaleOrder, self).unlink()

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'sale':
            return 'sale.mt_order_confirmed'
        elif 'state' in init_values and self.state == 'sent':
            return 'sale.mt_order_sent'
        return super(SaleOrder, self)._track_subtype(init_values)

    @api.multi
    @api.onchange('partner_shipping_id')
    def onchange_partner_shipping_id(self):
        """
        Trigger the change of fiscal position when the shipping address is modified.
        """
        fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, self.partner_shipping_id.id)
        if fiscal_position:
            self.fiscal_position_id = fiscal_position
        return {}

    @api.multi
    @api.onchange('partner_id')
    def onchange_partner_id(self):
        """
        Update the following fields when the partner is changed:
        - Pricelist
        - Payment term
        - Invoice address
        - Delivery address
        """
        if not self.partner_id:
            self.update({
                'partner_invoice_id': False,
                'partner_shipping_id': False,
                'payment_term_id': False,
                'fiscal_position_id': False,
            })
            return

        addr = self.partner_id.address_get(['delivery', 'invoice'])
        values = {
            'pricelist_id': self.partner_id.property_product_pricelist and self.partner_id.property_product_pricelist.id or False,
            'payment_term_id': self.partner_id.property_payment_term_id and self.partner_id.property_payment_term_id.id or False,
            'partner_invoice_id': addr['invoice'],
            'partner_shipping_id': addr['delivery'],
            'note': self.with_context(lang=self.partner_id.lang).env.user.company_id.sale_note,
        }

        if self.partner_id.user_id:
            values['user_id'] = self.partner_id.user_id.id
        if self.partner_id.team_id:
            values['team_id'] = self.partner_id.team_id.id
        self.update(values)

    @api.onchange('partner_id')
    def onchange_partner_id_warning(self):
        if not self.partner_id:
            return
        warning = {}
        title = False
        message = False
        partner = self.partner_id

        # If partner has no warning, check its company
        if partner.sale_warn == 'no-message' and partner.parent_id:
            partner = partner.parent_id

        if partner.sale_warn != 'no-message':
            # Block if partner only has warning but parent company is blocked
            if partner.sale_warn != 'block' and partner.parent_id and partner.parent_id.sale_warn == 'block':
                partner = partner.parent_id
            title =  _("Warning for %s") % partner.name
            message = partner.sale_warn_msg
            warning = {
                    'title': title,
                    'message': message,
            }
            if partner.sale_warn == 'block':
                self.update({'partner_id': False, 'partner_invoice_id': False, 'partner_shipping_id': False, 'pricelist_id': False})
                return {'warning': warning}

        if warning:
            return {'warning': warning}

    @api.model
    def create(self, vals):
        if vals.get('name', 'New') == 'New':
            vals['name'] = self.env['ir.sequence'].next_by_code('sale.order') or 'New'

        # Makes sure partner_invoice_id', 'partner_shipping_id' and 'pricelist_id' are defined
        if any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
            partner = self.env['res.partner'].browse(vals.get('partner_id'))
            addr = partner.address_get(['delivery', 'invoice'])
            vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice'])
            vals['partner_shipping_id'] = vals.setdefault('partner_shipping_id', addr['delivery'])
            vals['pricelist_id'] = vals.setdefault('pricelist_id', partner.property_product_pricelist and partner.property_product_pricelist.id)
        result = super(SaleOrder, self).create(vals)
        return result


    @api.multi
    def _prepare_invoice(self):
        """
        Prepare the dict of values to create the new invoice for a sales order. This method may be
        overridden to implement custom invoice generation (making sure to call super() to establish
        a clean extension chain).
        """
        self.ensure_one()
        journal_id = self.env['account.invoice'].default_get(['journal_id'])['journal_id']
        if not journal_id:
            raise UserError(_('Please define an accounting sale journal for this company.'))
        invoice_vals = {
            'name': self.client_order_ref or '',
            'origin': self.name,
            'type': 'out_invoice',
            'account_id': self.partner_invoice_id.property_account_receivable_id.id,
            'partner_id': self.partner_invoice_id.id,
            'journal_id': journal_id,
            'currency_id': self.pricelist_id.currency_id.id,
            'comment': self.note,
            'payment_term_id': self.payment_term_id.id,
            'fiscal_position_id': self.fiscal_position_id.id or self.partner_invoice_id.property_account_position_id.id,
            'company_id': self.company_id.id,
            'user_id': self.user_id and self.user_id.id,
            'team_id': self.team_id.id
        }
        return invoice_vals

    @api.multi
    def print_quotation(self):
        self.filtered(lambda s: s.state == 'draft').write({'state': 'sent'})
        return self.env['report'].get_action(self, 'sale.report_saleorder')

    @api.multi
    def action_view_invoice(self):
        invoice_ids = self.mapped('invoice_ids')
        imd = self.env['ir.model.data']
        action = imd.xmlid_to_object('account.action_invoice_tree1')
        list_view_id = imd.xmlid_to_res_id('account.invoice_tree')
        form_view_id = imd.xmlid_to_res_id('account.invoice_form')

        result = {
            'name': action.name,
            'help': action.help,
            'type': action.type,
            'views': [[list_view_id, 'tree'], [form_view_id, 'form'], [False, 'graph'], [False, 'kanban'], [False, 'calendar'], [False, 'pivot']],
            'target': action.target,
            'context': action.context,
            'res_model': action.res_model,
        }
        if len(invoice_ids) > 1:
            result['domain'] = "[('id','in',%s)]" % invoice_ids.ids
        elif len(invoice_ids) == 1:
            result['views'] = [(form_view_id, 'form')]
            result['res_id'] = invoice_ids.ids[0]
        else:
            result = {'type': 'ir.actions.act_window_close'}
        return result

    @api.multi
    def action_invoice_create(self, grouped=False, final=False):
        """
        Create the invoice associated to the SO.
        :param grouped: if True, invoices are grouped by SO id. If False, invoices are grouped by
                        (partner, currency)
        :param final: if True, refunds will be generated if necessary
        :returns: list of created invoices
        """
        inv_obj = self.env['account.invoice']
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        invoices = {}

        for order in self:
            group_key = order.id if grouped else (order.partner_id.id, order.currency_id.id)
            for line in order.order_line.sorted(key=lambda l: l.qty_to_invoice < 0):
                if float_is_zero(line.qty_to_invoice, precision_digits=precision):
                    continue
                if group_key not in invoices:
                    inv_data = order._prepare_invoice()
                    invoice = inv_obj.create(inv_data)
                    invoices[group_key] = invoice
                elif group_key in invoices:
                    vals = {}
                    if order.name not in invoices[group_key].origin.split(', '):
                        vals['origin'] = invoices[group_key].origin + ', ' + order.name
                    if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', '):
                        vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref
                    invoices[group_key].write(vals)
                if line.qty_to_invoice > 0:
                    line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
                elif line.qty_to_invoice < 0 and final:
                    line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)

        for invoice in invoices.values():
            if not invoice.invoice_line_ids:
                raise UserError(_('There is no invoicable line.'))
            # If invoice is negative, do a refund invoice instead
            if invoice.amount_untaxed < 0:
                invoice.type = 'out_refund'
                for line in invoice.invoice_line_ids:
                    line.quantity = -line.quantity
            # Use additional field helper function (for account extensions)
            for line in invoice.invoice_line_ids:
                line._set_additional_fields(invoice)
            # Necessary to force computation of taxes. In account_invoice, they are triggered
            # by onchanges, which are not triggered when doing a create.
            invoice.compute_taxes()

        return [inv.id for inv in invoices.values()]

    @api.multi
    def action_draft(self):
        orders = self.filtered(lambda s: s.state in ['cancel', 'sent'])
        orders.write({
            'state': 'draft',
            'procurement_group_id': False,
        })
        orders.mapped('order_line').mapped('procurement_ids').write({'sale_line_id': False})

    @api.multi
    def action_cancel(self):
        self.write({'state': 'cancel'})

    @api.multi
    def action_quotation_send(self):
        '''
        This function opens a window to compose an email, with the edi sale template message loaded by default
        '''
        self.ensure_one()
        ir_model_data = self.env['ir.model.data']
        try:
            template_id = ir_model_data.get_object_reference('sale', 'email_template_edi_sale')[1]
        except ValueError:
            template_id = False
        try:
            compose_form_id = ir_model_data.get_object_reference('mail', 'email_compose_message_wizard_form')[1]
        except ValueError:
            compose_form_id = False
        ctx = dict()
        ctx.update({
            'default_model': 'sale.order',
            'default_res_id': self.ids[0],
            'default_use_template': bool(template_id),
            'default_template_id': template_id,
            'default_composition_mode': 'comment',
            'mark_so_as_sent': True
        })
        return {
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'mail.compose.message',
            'views': [(compose_form_id, 'form')],
            'view_id': compose_form_id,
            'target': 'new',
            'context': ctx,
        }

    @api.multi
    def force_quotation_send(self):
        for order in self:
            email_act = order.action_quotation_send()
            if email_act and email_act.get('context'):
                email_ctx = email_act['context']
                email_ctx.update(default_email_from=order.company_id.email)
                order.with_context(email_ctx).message_post_with_template(email_ctx.get('default_template_id'))
        return True

    @api.multi
    def action_done(self):
        self.write({'state': 'done'})

    @api.model
    def _prepare_procurement_group(self):
        return {'name': self.name}

    @api.multi
    def action_confirm(self):
        for order in self:
            order.state = 'sale'
            order.confirmation_date = fields.Datetime.now()
            if self.env.context.get('send_email'):
                self.force_quotation_send()
            order.order_line._action_procurement_create()
            if not order.project_id:
                for line in order.order_line:
                    if line.product_id.invoice_policy == 'cost':
                        order._create_analytic_account()
                        break
        if self.env['ir.values'].get_default('sale.config.settings', 'auto_done_setting'):
            self.action_done()
        return True

    @api.multi
    def _create_analytic_account(self, prefix=None):
        for order in self:
            name = order.name
            if prefix:
                name = prefix + ": " + order.name
            analytic = self.env['account.analytic.account'].create({
                'name': name,
                'code': order.client_order_ref,
                'company_id': order.company_id.id,
                'partner_id': order.partner_id.id
            })
            order.project_id = analytic

    @api.multi
    def _notification_group_recipients(self, message, recipients, done_ids, group_data):
        group_user = self.env.ref('base.group_user')
        for recipient in recipients:
            if recipient.id in done_ids:
                continue
            if not recipient.user_ids:
                group_data['partner'] |= recipient
            else:
                group_data['user'] |= recipient
            done_ids.add(recipient.id)
        return super(SaleOrder, self)._notification_group_recipients(message, recipients, done_ids, group_data)

    @api.multi
    def order_lines_layouted(self):
        """
        Returns this order lines classified by sale_layout_category and separated in
        pages according to the category pagebreaks. Used to render the report.
        """
        self.ensure_one()
        report_pages = [[]]
        for category, lines in groupby(self.order_line, lambda l: l.layout_category_id):
            # If last added category induced a pagebreak, this one will be on a new page
            if report_pages[-1] and report_pages[-1][-1]['pagebreak']:
                report_pages.append([])
            # Append category to current report page
            report_pages[-1].append({
                'name': category and category.name or 'Uncategorized',
                'subtotal': category and category.subtotal,
                'pagebreak': category and category.pagebreak,
                'lines': list(lines)
            })

        return report_pages
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)
    journal_id = fields.Many2one('account.analytic.journal',
                                 string='Analytic Journal',
                                 required=True,
                                 ondelete='restrict',
                                 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)

    # Compute the cost based on the price type define into company
    # property_valuation_price_type property
    @api.v7
    def on_change_unit_amount(self,
                              cr,
                              uid,
                              id,
                              prod_id,
                              quantity,
                              company_id,
                              unit=False,
                              journal_id=False,
                              context=None):
        if context is None:
            context = {}
        if not journal_id:
            j_ids = self.pool.get('account.analytic.journal').search(
                cr, uid, [('type', '=', 'purchase')])
            journal_id = j_ids and j_ids[0] or False
        if not journal_id:
            return {}
        product_obj = self.pool.get('product.product')
        analytic_journal_obj = self.pool.get('account.analytic.journal')
        product_price_type_obj = self.pool.get('product.price.type')
        product_uom_obj = self.pool.get('product.uom')
        j_id = analytic_journal_obj.browse(cr,
                                           uid,
                                           journal_id,
                                           context=context)
        prod = product_obj.browse(cr, uid, prod_id, context=context)
        result = 0.0
        if prod_id:
            unit_obj = False
            if unit:
                unit_obj = product_uom_obj.browse(cr,
                                                  uid,
                                                  unit,
                                                  context=context)
            if not unit_obj or prod.uom_id.category_id.id != unit_obj.category_id.id:
                unit = prod.uom_id.id
            if j_id.type == 'purchase':
                if not unit_obj or prod.uom_po_id.category_id.id != unit_obj.category_id.id:
                    unit = prod.uom_po_id.id
        if j_id.type <> 'sale':
            a = prod.property_account_expense_id.id
            if not a:
                a = prod.categ_id.property_account_expense_categ_id.id
        else:
            a = prod.property_account_income_id.id
            if not a:
                a = prod.categ_id.property_account_income_categ_id.id

        flag = False
        # Compute based on pricetype
        product_price_type_ids = product_price_type_obj.search(
            cr, uid, [('field', '=', 'standard_price')], context=context)
        pricetype = product_price_type_obj.browse(cr,
                                                  uid,
                                                  product_price_type_ids,
                                                  context=context)[0]
        if journal_id:
            journal = analytic_journal_obj.browse(cr,
                                                  uid,
                                                  journal_id,
                                                  context=context)
            if journal.type == 'sale':
                product_price_type_ids = product_price_type_obj.search(
                    cr, uid, [('field', '=', 'list_price')], context=context)
                if product_price_type_ids:
                    pricetype = product_price_type_obj.browse(
                        cr, uid, product_price_type_ids, context=context)[0]
        # Take the company currency as the reference one
        if pricetype.field == 'list_price':
            flag = True
        ctx = context.copy()
        if unit:
            # price_get() will respect a 'uom' in its context, in order
            # to return a default price for those units
            ctx['uom'] = unit
        amount_unit = prod.price_get(pricetype.field, context=ctx)
        if amount_unit:
            amount_unit = amount_unit[prod.id]
        else:
            amount_unit = 0.0

        amount = amount_unit * quantity or 0.0
        cur_record = self.browse(cr, uid, id, context=context)
        currency = cur_record.exists(
        ) and cur_record.currency_id or prod.company_id.currency_id
        result = round(amount, currency.decimal_places)
        if not flag:
            result *= -1
        return {
            'value': {
                'amount': result,
                'general_account_id': a,
                'product_uom_id': unit
            }
        }

    @api.v8
    @api.onchange('product_id', 'product_uom_id')
    def on_change_unit_amount(self):
        product_price_type_obj = self.env['product.price.type']

        journal_id = self.journal_id
        if not journal_id:
            journal_id = self.env['account.analytic.journal'].search(
                [('type', '=', 'purchase')], limit=1)
        if not journal_id or not self.product_id:
            return {}

        result = 0.0
        if self.product_id:
            unit = self.product_uom_id.id
            if not self.product_uom_id or self.product_id.uom_id.category_id.id != self.product_uom_id.category_id.id:
                unit = self.product_id.uom_id.id
            if journal_id.type == 'purchase':
                if not self.product_uom_id or self.product_id.uom_po_id.category_id.id != self.product_uom_id.category_id.id:
                    unit = self.product_id.uom_po_id.id
        if journal_id.type != 'sale':
            account = self.product_id.property_account_expense_id.id or self.product_id.categ_id.property_account_expense_categ_id.id
            if not account:
                raise UserError(_('There is no expense account defined ' \
                                'for this product: "%s" (id:%d).') % \
                                (self.product_id.name, self.product_id.id,))
        else:
            account = self.product_id.property_account_income_id.id or self.product_id.categ_id.property_account_income_categ_id.id
            if not account:
                raise UserError(_('There is no income account defined ' \
                                'for this product: "%s" (id:%d).') % \
                                (self.product_id.name, self.product_id.id,))

        # Compute based on pricetype
        if journal_id.type == 'sale':
            pricetype = product_price_type_obj.search(
                [('field', '=', 'list_price')], limit=1)
        else:
            pricetype = product_price_type_obj.search(
                [('field', '=', 'standard_price')], limit=1)

        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
        amount_unit = self.product_id.with_context(ctx).price_get(
            pricetype.field)[self.product_id.id]
        amount = amount_unit * self.unit_amount or 0.0
        result = round(amount, self.currency_id.decimal_places)
        if pricetype.field != 'list_price':
            result *= -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 AccountBankStatement(models.Model):

    @api.one
    @api.depends('line_ids', 'balance_start', 'line_ids.amount', 'balance_end_real')
    def _end_balance(self):
        self.total_entry_encoding = sum([line.amount for line in self.line_ids])
        self.balance_end = self.balance_start + self.total_entry_encoding
        self.difference = self.balance_end_real - self.balance_end

    @api.multi
    def _is_difference_zero(self):
        for bank_stmt in self:
            bank_stmt.is_difference_zero = float_is_zero(bank_stmt.difference, precision_digits=bank_stmt.currency_id.decimal_places)

    @api.one
    @api.depends('journal_id')
    def _compute_currency(self):
        self.currency_id = self.journal_id.currency_id or self.company_id.currency_id

    @api.one
    @api.depends('line_ids.journal_entry_ids')
    def _check_lines_reconciled(self):
        self.all_lines_reconciled = all([line.journal_entry_ids.ids or line.account_id.id for line in self.line_ids])

    @api.model
    def _default_journal(self):
        journal_type = self.env.context.get('journal_type', False)
        company_id = self.env['res.company']._company_default_get('account.bank.statement').id
        if journal_type:
            journals = self.env['account.journal'].search([('type', '=', journal_type), ('company_id', '=', company_id)])
            if journals:
                return journals[0]
        return False

    @api.multi
    def _get_opening_balance(self, journal_id):
        last_bnk_stmt = self.search([('journal_id', '=', journal_id)], limit=1)
        if last_bnk_stmt:
            return last_bnk_stmt.balance_end
        return 0

    @api.multi
    def _set_opening_balance(self, journal_id):
        self.balance_start = self._get_opening_balance(journal_id)

    @api.model
    def _default_opening_balance(self):
        #Search last bank statement and set current opening balance as closing balance of previous one
        journal_id = self._context.get('default_journal_id', False) or self._context.get('journal_id', False)
        if journal_id:
            return self._get_opening_balance(journal_id)
        return 0

    _name = "account.bank.statement"
    _description = "Bank Statement"
    _order = "date desc, id desc"
    _inherit = ['mail.thread']

    name = fields.Char(string='Reference', states={'open': [('readonly', False)]}, copy=False, readonly=True)
    reference = fields.Char(string='External Reference', states={'open': [('readonly', False)]}, copy=False, readonly=True, help="Used to hold the reference of the external mean that created this statement (name of imported file, reference of online synchronization...)")
    date = fields.Date(required=True, states={'confirm': [('readonly', True)]}, select=True, copy=False, default=fields.Date.context_today)
    date_done = fields.Datetime(string="Closed On")
    balance_start = fields.Monetary(string='Starting Balance', states={'confirm': [('readonly', True)]}, default=_default_opening_balance)
    balance_end_real = fields.Monetary('Ending Balance', states={'confirm': [('readonly', True)]})
    state = fields.Selection([('open', 'New'), ('confirm', 'Validated')], string='Status', required=True, readonly=True, copy=False, default='open')
    currency_id = fields.Many2one('res.currency', compute='_compute_currency', oldname='currency', string="Currency")
    journal_id = fields.Many2one('account.journal', string='Journal', required=True, states={'confirm': [('readonly', True)]}, default=_default_journal)
    journal_type = fields.Selection(related='journal_id.type', help="Technical field used for usability purposes")
    company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', store=True, readonly=True,
        default=lambda self: self.env['res.company']._company_default_get('account.bank.statement'))

    total_entry_encoding = fields.Monetary('Transactions Subtotal', compute='_end_balance', store=True, help="Total of transaction lines.")
    balance_end = fields.Monetary('Computed Balance', compute='_end_balance', store=True, help='Balance as calculated based on Opening Balance and transaction lines')
    difference = fields.Monetary(compute='_end_balance', store=True, help="Difference between the computed ending balance and the specified ending balance.")

    line_ids = fields.One2many('account.bank.statement.line', 'statement_id', string='Statement lines', states={'confirm': [('readonly', True)]}, copy=True)
    move_line_ids = fields.One2many('account.move.line', 'statement_id', string='Entry lines', states={'confirm': [('readonly', True)]})
    all_lines_reconciled = fields.Boolean(compute='_check_lines_reconciled')
    user_id = fields.Many2one('res.users', string='Responsible', required=False, default=lambda self: self.env.user)
    cashbox_start_id = fields.Many2one('account.bank.statement.cashbox', string="Starting Cashbox")
    cashbox_end_id = fields.Many2one('account.bank.statement.cashbox', string="Ending Cashbox")
    is_difference_zero = fields.Boolean(compute='_is_difference_zero', string='Is zero', help="Check if difference is zero.")

    @api.onchange('journal_id')
    def onchange_journal_id(self):
        self._set_opening_balance(self.journal_id.id)

    @api.multi
    def _balance_check(self):
        for stmt in self:
            if not stmt.currency_id.is_zero(stmt.difference):
                if stmt.journal_type == 'cash':
                    if stmt.difference < 0.0:
                        account = stmt.journal_id.loss_account_id
                        name = _('Loss')
                    else:
                        # statement.difference > 0.0
                        account = stmt.journal_id.profit_account_id
                        name = _('Profit')
                    if not account:
                        raise UserError(_('There is no account defined on the journal %s for %s involved in a cash difference.') % (stmt.journal_id.name, name))

                    values = {
                        'statement_id': stmt.id,
                        'account_id': account.id,
                        'amount': stmt.difference,
                        'name': _("Cash difference observed during the counting (%s)") % name,
                    }
                    self.env['account.bank.statement.line'].create(values)
                else:
                    balance_end_real = formatLang(self.env, stmt.balance_end_real, currency_obj=stmt.currency_id)
                    balance_end = formatLang(self.env, stmt.balance_end, currency_obj=stmt.currency_id)
                    raise UserError(_('The ending balance is incorrect !\nThe expected balance (%s) is different from the computed one. (%s)')
                        % (balance_end_real, balance_end))
        return True

    @api.model
    def create(self, vals):
        if not vals.get('name'):
            journal_id = vals.get('journal_id', self._context.get('default_journal_id', False))
            journal = self.env['account.journal'].browse(journal_id)
            vals['name'] = journal.sequence_id.with_context(ir_sequence_date=vals.get('date')).next_by_id()
        return super(AccountBankStatement, self).create(vals)

    @api.multi
    def unlink(self):
        for statement in self:
            if statement.state != 'open':
                raise UserError(_('In order to delete a bank statement, you must first cancel it to delete related journal items.'))
            # Explicitly unlink bank statement lines so it will check that the related journal entries have been deleted first
            statement.line_ids.unlink()
        return super(AccountBankStatement, self).unlink()

    @api.multi
    def open_cashbox_id(self):
        context = dict(self.env.context or {})
        if context.get('cashbox_id'):
            context['active_id'] = self.id
            return {
                'name': _('Cash Control'),
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'account.bank.statement.cashbox',
                'view_id': self.env.ref('account.view_account_bnk_stmt_cashbox').id,
                'type': 'ir.actions.act_window',
                'res_id': self.env.context.get('cashbox_id'),
                'context': context,
                'target': 'new'
            }

    @api.multi
    def button_cancel(self):
        for statement in self:
            if any(line.journal_entry_ids.ids for line in statement.line_ids):
                raise UserError(_('A statement cannot be canceled when its lines are reconciled.'))
        self.state = 'open'

    @api.multi
    def check_confirm_bank(self):
        if self.journal_type == 'cash' and not self.currency_id.is_zero(self.difference):
            action_rec = self.env['ir.model.data'].xmlid_to_object('account.action_view_account_bnk_stmt_check')
            if action_rec:
                action = action_rec.read([])[0]
                return action
        return self.button_confirm_bank()

    @api.multi
    def button_confirm_bank(self):
        self._balance_check()
        statements = self.filtered(lambda r: r.state == 'open')
        for statement in statements:
            moves = self.env['account.move']
            for st_line in statement.line_ids:
                if st_line.account_id and not st_line.journal_entry_ids.ids:
                    st_line.fast_counterpart_creation()
                elif not st_line.journal_entry_ids.ids:
                    raise UserError(_('All the account entries lines must be processed in order to close the statement.'))
                moves = (moves | st_line.journal_entry_ids)
            if moves:
                moves.post()
            statement.message_post(body=_('Statement %s confirmed, journal items were created.') % (statement.name,))
        statements.link_bank_to_partner()
        statements.write({'state': 'confirm', 'date_done': time.strftime("%Y-%m-%d %H:%M:%S")})

    @api.multi
    def button_journal_entries(self):
        context = dict(self._context or {})
        context['journal_id'] = self.journal_id.id
        return {
            'name': _('Journal Items'),
            'view_type': 'form',
            'view_mode': 'tree',
            'res_model': 'account.move.line',
            'view_id': False,
            'type': 'ir.actions.act_window',
            'domain': [('statement_id', 'in', self.ids)],
            'context': context,
        }

    @api.multi
    def button_open(self):
        """ Changes statement state to Running."""
        for statement in self:
            if not statement.name:
                context = {'ir_sequence_date', statement.date}
                if statement.journal_id.sequence_id:
                    st_number = statement.journal_id.sequence_id.with_context(context).next_by_id()
                else:
                    SequenceObj = self.env['ir.sequence']
                    st_number = SequenceObj.with_context(context).next_by_code('account.bank.statement')
                statement.name = st_number
            statement.state = 'open'

    @api.multi
    def reconciliation_widget_preprocess(self):
        """ Get statement lines of the specified statements or all unreconciled statement lines and try to automatically reconcile them / find them a partner.
            Return ids of statement lines left to reconcile and other data for the reconciliation widget.
        """
        statements = self
        bsl_obj = self.env['account.bank.statement.line']

        # NB : The field account_id can be used at the statement line creation/import to avoid the reconciliation process on it later on,
        # this is why we filter out statements lines where account_id is set
        st_lines_filter = [('journal_entry_ids', '=', False), ('account_id', '=', False)]
        if statements:
            st_lines_filter += [('statement_id', 'in', statements.ids)]

        # Try to automatically reconcile statement lines
        automatic_reconciliation_entries = []
        st_lines_left = self.env['account.bank.statement.line']
        for st_line in bsl_obj.search(st_lines_filter):
            res = st_line.auto_reconcile()
            if not res:
                st_lines_left = (st_lines_left | st_line)
            else:
                automatic_reconciliation_entries.append(res.ids)

        # Try to set statement line's partner
        for st_line in st_lines_left:
            if st_line.name and not st_line.partner_id:
                additional_domain = [('ref', '=', st_line.name)]
                match_recs = st_line.get_move_lines_for_reconciliation(limit=1, additional_domain=additional_domain, overlook_partner=True)
                if match_recs and match_recs[0].partner_id:
                    st_line.write({'partner_id': match_recs[0].partner_id.id})

        # Collect various informations for the reconciliation widget
        notifications = []
        num_auto_reconciled = len(automatic_reconciliation_entries)
        if num_auto_reconciled > 0:
            auto_reconciled_message = num_auto_reconciled > 1 \
                and _("%d transactions were automatically reconciled.") % num_auto_reconciled \
                or _("1 transaction was automatically reconciled.")
            notifications += [{
                'type': 'info',
                'message': auto_reconciled_message,
                'details': {
                    'name': _("Automatically reconciled items"),
                    'model': 'account.move',
                    'ids': automatic_reconciliation_entries
                }
            }]

        lines = []
        for el in statements:
            lines.extend(el.line_ids.ids)
        lines = list(set(lines))

        return {
            'st_lines_ids': st_lines_left.ids,
            'notifications': notifications,
            'statement_name': len(statements) == 1 and statements[0].name or False,
            'num_already_reconciled_lines': statements and bsl_obj.search_count([('journal_entry_ids', '!=', False), ('id', 'in', lines)]) or 0,
        }

    @api.multi
    def link_bank_to_partner(self):
        for statement in self:
            for st_line in statement.line_ids:
                if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id != st_line.partner_id:
                    st_line.bank_account_id.partner_id = st_line.partner_id
Beispiel #16
0
class PurchaseOrderLine(models.Model):
    _name = 'purchase.order.line'
    _description = 'Purchase Order Line'

    @api.depends('product_qty', 'price_unit', 'taxes_id')
    def _compute_amount(self):
        for line in self:
            taxes = line.taxes_id.compute_all(line.price_unit, line.order_id.currency_id, line.product_qty, product=line.product_id, partner=line.order_id.partner_id)
            line.update({
                'price_tax': taxes['total_included'] - taxes['total_excluded'],
                'price_total': taxes['total_included'],
                'price_subtotal': taxes['total_excluded'],
            })

    @api.depends('invoice_lines.invoice_id.state')
    def _compute_qty_invoiced(self):
        for line in self:
            qty = 0.0
            for inv_line in line.invoice_lines:
                if inv_line.invoice_id.state not in ['cancel']:
                    qty += inv_line.uom_id._compute_qty_obj(inv_line.uom_id, inv_line.quantity, line.product_uom)
            line.qty_invoiced = qty

    @api.depends('order_id.state', 'move_ids.state')
    def _compute_qty_received(self):
        productuom = self.env['product.uom']
        for line in self:
            if line.order_id.state not in ['purchase', 'done']:
                line.qty_received = 0.0
                continue
            if line.product_id.type not in ['consu', 'product']:
                line.qty_received = line.product_qty
                continue
            bom_delivered = self.sudo()._get_bom_delivered(line.sudo())
            if bom_delivered and any(bom_delivered.values()):
                total = line.product_qty
            elif bom_delivered:
                total = 0.0
            else:
                total = 0.0
                for move in line.move_ids:
                    if move.state == 'done':
                        if move.product_uom != line.product_uom:
                            total += productuom._compute_qty_obj(move.product_uom, move.product_uom_qty, line.product_uom)
                        else:
                            total += move.product_uom_qty
            line.qty_received = total

    def _get_bom_delivered(self, line):
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        bom_delivered = {}
        # There is no dependencies between purchase and mrp
        if 'mrp.bom' in self.env:
            # In the case of a kit, we need to check if all components are shipped. We use a all or
            # nothing policy. A product can have several BoMs, we don't know which one was used when the
            # delivery was created.
            for bom in line.product_id.product_tmpl_id.bom_ids:
                if bom.type != 'phantom':
                    continue
                bom_delivered[bom.id] = False
                product_uom_qty_bom = self.env['product.uom']._compute_qty_obj(line.product_uom, line.product_qty, bom.product_uom)
                bom_exploded = self.env['mrp.bom']._bom_explode(bom, line.product_id, product_uom_qty_bom)[0]
                for bom_line in bom_exploded:
                    qty = 0.0
                    for move in line.move_ids:
                        if move.state == 'done' and move.product_id.id == bom_line.get('product_id', False):
                            qty += self.env['product.uom']._compute_qty(move.product_uom.id, move.product_uom_qty, bom_line['product_uom'])
                    if float_compare(qty, bom_line['product_qty'], precision_digits=precision) < 0:
                        bom_delivered[bom.id] = False
                        break
                    else:
                        bom_delivered[bom.id] = True
        return bom_delivered



    name = fields.Text(string='Description', required=True)
    product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True)
    date_planned = fields.Datetime(string='Scheduled Date', required=True, index=True)
    taxes_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
    product_uom = fields.Many2one('product.uom', string='Product Unit of Measure', required=True)
    product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True, required=True)
    move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, ondelete='set null', copy=False)
    price_unit = fields.Float(string='Unit Price', required=True, digits=dp.get_precision('Product Price'))

    price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', store=True)
    price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True)
    price_tax = fields.Monetary(compute='_compute_amount', string='Tax', store=True)

    order_id = fields.Many2one('purchase.order', string='Order Reference', index=True, required=True, ondelete='cascade')
    account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account', domain=[('account_type', '=', 'normal')])
    company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, readonly=True)
    state = fields.Selection(related='order_id.state', stored=True)

    invoice_lines = fields.One2many('account.invoice.line', 'purchase_line_id', string="Invoice Lines", readonly=True, copy=False)

    # Replace by invoiced Qty
    qty_invoiced = fields.Float(compute='_compute_qty_invoiced', string="Billed Qty", store=True)
    qty_received = fields.Float(compute='_compute_qty_received', string="Received Qty", store=True)

    partner_id = fields.Many2one('res.partner', related='order_id.partner_id', string='Partner', readonly=True, store=True)
    currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True)
    date_order = fields.Datetime(related='order_id.date_order', string='Order Date', readonly=True)
    procurement_ids = fields.One2many('procurement.order', 'purchase_line_id', string='Associated Procurements', copy=False)

    @api.multi
    def _get_stock_move_price_unit(self):
        self.ensure_one()
        line = self[0]
        order = line.order_id
        price_unit = line.price_unit
        if line.taxes_id:
            price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit, currency=line.order_id.currency_id, quantity=1.0)['total_excluded']
        if line.product_uom.id != line.product_id.uom_id.id:
            price_unit *= line.product_uom.factor / line.product_id.uom_id.factor
        if order.currency_id != order.company_id.currency_id:
            price_unit = order.currency_id.compute(price_unit, order.company_id.currency_id, round=False)
        return price_unit

    @api.multi
    def _create_stock_moves(self, picking):
        moves = self.env['stock.move']
        done = self.env['stock.move'].browse()
        for line in self:
            price_unit = line._get_stock_move_price_unit()

            template = {
                'name': line.name or '',
                'product_id': line.product_id.id,
                'product_uom': line.product_uom.id,
                'date': line.order_id.date_order,
                'date_expected': line.date_planned,
                'location_id': line.order_id.partner_id.property_stock_supplier.id,
                'location_dest_id': line.order_id._get_destination_location(),
                'picking_id': picking.id,
                'partner_id': line.order_id.dest_address_id.id,
                'move_dest_id': False,
                'state': 'draft',
                'purchase_line_id': line.id,
                'company_id': line.order_id.company_id.id,
                'price_unit': price_unit,
                'picking_type_id': line.order_id.picking_type_id.id,
                'group_id': line.order_id.group_id.id,
                'procurement_id': False,
                'origin': line.order_id.name,
                'route_ids': line.order_id.picking_type_id.warehouse_id and [(6, 0, [x.id for x in line.order_id.picking_type_id.warehouse_id.route_ids])] or [],
                'warehouse_id':line.order_id.picking_type_id.warehouse_id.id,
            }

            # Fullfill all related procurements with this po line
            diff_quantity = line.product_qty
            for procurement in line.procurement_ids:
                procurement_qty = procurement.product_uom._compute_qty_obj(procurement.product_uom, procurement.product_qty, line.product_uom)
                tmp = template.copy()
                tmp.update({
                    'product_uom_qty': min(procurement_qty, diff_quantity),
                    'move_dest_id': procurement.move_dest_id.id,  #move destination is same as procurement destination
                    'procurement_id': procurement.id,
                    'propagate': procurement.rule_id.propagate,
                })
                done += moves.create(tmp)
                diff_quantity -= min(procurement_qty, diff_quantity)
            if float_compare(diff_quantity, 0.0, precision_rounding=line.product_uom.rounding) > 0:
                template['product_uom_qty'] = diff_quantity
                done += moves.create(template)
        return done

    @api.multi
    def unlink(self):
        for line in self:
            if line.order_id.state in ['approved', 'done']:
                raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') %(line.state,))
            for proc in line.procurement_ids:
                proc.message_post(body=_('Purchase order line deleted.'))
            line.procurement_ids.filtered(lambda r: r.state != 'cancel').write({'state': 'exception'})
        return super(PurchaseOrderLine, self).unlink()

    @api.model
    def _get_date_planned(self, seller, po=False):
        """Return the datetime value to use as Schedule Date (``date_planned``) for
           PO Lines that correspond to the given product.seller_ids,
           when ordered at `date_order_str`.

           :param browse_record | False product: product.product, used to
               determine delivery delay thanks to the selected seller field (if False, default delay = 0)
           :param browse_record | False po: purchase.order, necessary only if
               the PO line is not yet attached to a PO.
           :rtype: datetime
           :return: desired Schedule Date for the PO line
        """
        date_order = po.date_order if po else self.order_id.date_order
        if date_order:
            return datetime.strptime(date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=seller.delay if seller else 0)
        else:
            return datetime.today() + relativedelta(days=seller.delay if seller else 0)

    @api.onchange('product_id')
    def onchange_product_id(self):
        result = {}
        if not self.product_id:
            return result

        # Reset date, price and quantity since _onchange_quantity will provide default values
        self.date_planned = datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
        self.price_unit = self.product_qty = 0.0
        self.product_uom = self.product_id.uom_po_id or self.product_id.uom_id
        result['domain'] = {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]}

        product_lang = self.product_id.with_context({
            'lang': self.partner_id.lang,
            'partner_id': self.partner_id.id,
        })
        self.name = product_lang.display_name
        if product_lang.description_purchase:
            self.name += '\n' + product_lang.description_purchase

        fpos = self.order_id.fiscal_position_id
        if self.env.uid == SUPERUSER_ID:
            company_id = self.env.user.company_id.id
            self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id.filtered(lambda r: r.company_id.id == company_id))
        else:
            self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)

        self._suggest_quantity()
        self._onchange_quantity()

        return result

    @api.onchange('product_qty', 'product_uom')
    def _onchange_quantity(self):
        if not self.product_id:
            return

        seller = self.product_id._select_seller(
            self.product_id,
            partner_id=self.partner_id,
            quantity=self.product_qty,
            date=self.order_id.date_order and self.order_id.date_order[:10],
            uom_id=self.product_uom)

        if seller or not self.date_planned:
            self.date_planned = self._get_date_planned(seller).strftime(DEFAULT_SERVER_DATETIME_FORMAT)

        if not seller:
            return

        price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, self.taxes_id) if seller else 0.0
        if price_unit and seller and self.order_id.currency_id and seller.currency_id != self.order_id.currency_id:
            price_unit = seller.currency_id.compute(price_unit, self.order_id.currency_id)

        if seller and self.product_uom and seller.product_uom != self.product_uom:
            price_unit = self.env['product.uom']._compute_price(seller.product_uom.id, price_unit, to_uom_id=self.product_uom.id)

        self.price_unit = price_unit

    def _suggest_quantity(self):
        '''
        Suggest a minimal quantity based on the seller
        '''
        if not self.product_id:
            return

        seller_min_qty = self.product_id.seller_ids\
            .filtered(lambda r: r.name == self.order_id.partner_id)\
            .sorted(key=lambda r: r.min_qty)
        if seller_min_qty:
            self.product_qty = seller_min_qty[0].min_qty or 1.0
            self.product_uom = seller_min_qty[0].product_uom
        else:
            self.product_qty = 1.0
class AccountInvoice(models.Model):
    """
    about name_get and display name:
    * in this model name_get and name_search are re-defined so we overwrite
    them
    * we add display_name to replace name field use, we add
     with search funcion. This field is used then for name_get and name_search

    Acccoding this https://www.odoo.com/es_ES/forum/ayuda-1/question/
    how-to-override-name-get-method-in-new-api-61228
    we should modify name_get, we do that by creating a helper display_name
    field and also overwriting name_get to use it
    """
    _inherit = "account.invoice"
    _order = "date_invoice desc, document_number desc, number desc, id desc"
    # _order = "document_number desc, number desc, id desc"

    report_amount_tax = fields.Monetary(
        string='Tax', compute='_compute_report_amount_and_taxes')
    report_amount_untaxed = fields.Monetary(
        string='Untaxed Amount', compute='_compute_report_amount_and_taxes')
    report_tax_line_ids = fields.One2many(
        compute="_compute_report_amount_and_taxes",
        comodel_name='account.invoice.tax',
        string='Taxes')
    available_journal_document_type_ids = fields.Many2many(
        'account.journal.document.type',
        compute='get_available_journal_document_types',
        string='Available Journal Document Types',
    )
    journal_document_type_id = fields.Many2one(
        'account.journal.document.type',
        'Document Type',
        readonly=True,
        ondelete='restrict',
        copy=False,
        auto_join=True,
        states={'draft': [('readonly', False)]})
    # we add this fields so we can search, group and analyze by this one
    document_type_id = fields.Many2one(
        related='journal_document_type_id.document_type_id',
        copy=False,
        readonly=True,
        store=True,
        auto_join=True,
    )
    document_sequence_id = fields.Many2one(
        related='journal_document_type_id.sequence_id',
        readonly=True,
    )
    document_number = fields.Char(
        string='Document Number',
        copy=False,
        readonly=True,
        states={'draft': [('readonly', False)]},
        track_visibility='onchange',
    )
    display_name = fields.Char(
        compute='_get_display_name',
        string='Document Reference',
    )
    next_number = fields.Integer(
        compute='_get_next_number',
        string='Next Number',
    )
    use_documents = fields.Boolean(related='journal_id.use_documents',
                                   string='Use Documents?',
                                   readonly=True)
    localization = fields.Selection(
        related='company_id.localization',
        readonly=True,
    )
    document_type_internal_type = fields.Selection(
        related='document_type_id.internal_type',
        readonly=True,
    )

    @api.multi
    def _get_tax_amount_by_group(self):
        """
        we can not inherit because of function design, we overwrite
        """
        self.ensure_one()
        res = {}
        currency = self.currency_id or self.company_id.currency_id
        for line in self.report_tax_line_ids:
            res.setdefault(line.tax_id.tax_group_id, 0.0)
            res[line.tax_id.tax_group_id] += line.amount
        res = sorted(res.items(), key=lambda l: l[0].sequence)
        res = map(
            lambda l:
            (l[0].name, formatLang(self.env, l[1], currency_obj=currency)),
            res)
        return res

    @api.depends('amount_untaxed', 'amount_tax', 'tax_line_ids',
                 'document_type_id')
    def _compute_report_amount_and_taxes(self):
        for invoice in self:
            taxes_included = (invoice.document_type_id and
                              invoice.document_type_id.get_taxes_included()
                              or False)
            if not taxes_included:
                report_amount_tax = invoice.amount_tax
                report_amount_untaxed = invoice.amount_untaxed
                not_included_taxes = invoice.tax_line_ids
            else:
                included_taxes = invoice.tax_line_ids.filtered(
                    lambda x: x.tax_id in taxes_included)
                not_included_taxes = (invoice.tax_line_ids - included_taxes)
                report_amount_tax = sum(not_included_taxes.mapped('amount'))
                report_amount_untaxed = invoice.amount_untaxed + sum(
                    included_taxes.mapped('amount'))
            invoice.report_amount_tax = report_amount_tax
            invoice.report_amount_untaxed = report_amount_untaxed
            invoice.report_tax_line_ids = not_included_taxes

    @api.multi
    @api.depends(
        'journal_id.sequence_id.number_next_actual',
        'journal_document_type_id.sequence_id.number_next_actual',
    )
    def _get_next_number(self):
        """
        show next number only for invoices without number and on draft state
        """
        for invoice in self.filtered(
                lambda x: not x.display_name and x.state == 'draft'):
            if invoice.use_documents:
                sequence = invoice.journal_document_type_id.sequence_id
            elif (invoice.type in ['out_refund', 'in_refund']
                  and invoice.journal_id.refund_sequence):
                sequence = invoice.journal_id.refund_sequence_id
            else:
                sequence = invoice.journal_id.sequence_id
            # we must check if sequence use date ranges
            if not sequence.use_date_range:
                invoice.next_number = sequence.number_next_actual
            else:
                dt = fields.Date.today()
                if self.env.context.get('ir_sequence_date'):
                    dt = self.env.context.get('ir_sequence_date')
                seq_date = self.env['ir.sequence.date_range'].search(
                    [('sequence_id', '=', sequence.id),
                     ('date_from', '<=', dt), ('date_to', '>=', dt)],
                    limit=1)
                if not seq_date:
                    seq_date = sequence._create_date_range_seq(dt)
                invoice.next_number = seq_date.number_next_actual

    @api.multi
    def name_get(self):
        TYPES = {
            'out_invoice': _('Invoice'),
            'in_invoice': _('Vendor Bill'),
            'out_refund': _('Refund'),
            'in_refund': _('Vendor Refund'),
        }
        result = []
        for inv in self:
            result.append(
                (inv.id, "%s %s" %
                 (inv.display_name or TYPES[inv.type], inv.name or '')))
        return result

    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100):
        args = args or []
        recs = self.browse()
        if name:
            recs = self.search(
                ['|', ('document_number', '=', name),
                 ('number', '=', name)] + args,
                limit=limit)
        if not recs:
            recs = self.search([('name', operator, name)] + args, limit=limit)
        return recs.name_get()

    @api.multi
    @api.constrains(
        'journal_id',
        'partner_id',
        'journal_document_type_id',
    )
    def _get_document_type(self):
        """ Como los campos responsible y journal document type no los
        queremos hacer funcion porque no queremos que sus valores cambien nunca
        y como con la funcion anterior solo se almacenan solo si se crea desde
        interfaz, hacemos este hack de constraint para computarlos si no estan
        computados"""
        for rec in self:
            if (not rec.journal_document_type_id
                    and rec.available_journal_document_type_ids):
                rec.journal_document_type_id = (
                    rec._get_available_journal_document_types(
                        rec.journal_id, rec.type,
                        rec.partner_id).get('journal_document_type'))

    @api.multi
    @api.depends('move_name', 'document_number',
                 'document_type_id.doc_code_prefix')
    def _get_display_name(self):
        """
        If move_name then invoice has been validated, then:
        * If document number and document type, we show them
        * Else, we show move_name
        """
        # al final no vimos porque necesiamos que este el move name, es util
        # mostrar igual si existe el numero, por ejemplo si es factura de
        # proveedor
        # if self.document_number and self.document_type_id and self.move_name:
        for rec in self:
            if rec.document_number and rec.document_type_id:
                display_name = ("%s%s" % (rec.document_type_id.doc_code_prefix
                                          or '', rec.document_number))
            else:
                display_name = rec.move_name
            rec.display_name = display_name

    @api.multi
    def check_use_documents(self):
        """
        check invoices has document class but journal require it (we check
        all invoices, not only argentinian ones)
        """
        without_doucument_class = self.filtered(
            lambda r: (not r.document_type_id and r.journal_id.use_documents))
        if without_doucument_class:
            raise UserError(
                _('Some invoices have a journal that require a document but not '
                  'document type has been selected.\n'
                  'Invoices ids: %s' % without_doucument_class.ids))

    @api.multi
    def get_localization_invoice_vals(self):
        """
        Function to be inherited by different localizations and add custom
        data to invoice on invoice validation
        """
        self.ensure_one()
        return {}

    @api.multi
    def action_move_create(self):
        """
        We add currency rate on move creation so it can be used by electronic
        invoice later on action_number
        """
        self.check_use_documents()
        res = super(AccountInvoice, self).action_move_create()
        self.set_document_data()
        return res

    @api.multi
    def set_document_data(self):
        """
        If journal document dont have any sequence, then document number
        must be set on the account.invoice and we use thisone
        A partir de este metodo no debería haber errores porque el modulo de
        factura electronica ya habria pedido el cae. Lo ideal sería hacer todo
        esto antes que se pida el cae pero tampoco se pueden volver a atras los
        conusmos de secuencias. TODO mejorar esa parte
        """
        # We write document_number field with next invoice number by
        # document type
        for invoice in self:
            _logger.info(
                'Setting document data on account.invoice and account.move')
            journal_document_type = invoice.journal_document_type_id
            inv_vals = self.get_localization_invoice_vals()
            if invoice.use_documents:
                if not invoice.document_number:
                    if not invoice.journal_document_type_id.sequence_id:
                        raise UserError(
                            _('Error!. Please define sequence on the journal '
                              'related documents to this invoice or set the '
                              'document number.'))
                    document_number = (
                        journal_document_type.sequence_id.next_by_id())
                    inv_vals['document_number'] = document_number
                # for canelled invoice number that still has a document_number
                # if validated again we use old document_number
                # also use this for supplier invoices
                else:
                    document_number = invoice.document_number
                invoice.move_id.write({
                    'document_type_id':
                    (journal_document_type.document_type_id.id),
                    'document_number':
                    document_number,
                })
            invoice.write(inv_vals)
        return True

    @api.multi
    @api.depends('journal_id', 'partner_id', 'company_id')
    def get_available_journal_document_types(self):
        """
        This function should only be inherited if you need to add another
        "depends", for eg, if you need to add a depend on "new_field" you
        should add:

        @api.depends('new_field')
        def _get_available_journal_document_types(
                self, journal, invoice_type, partner):
            return super(
                AccountInvoice, self)._get_available_journal_document_types(
                    journal, invoice_type, partner)
        """
        for invoice in self:
            res = invoice._get_available_journal_document_types(
                invoice.journal_id, invoice.type, invoice.partner_id)
            invoice.available_journal_document_type_ids = res[
                'available_journal_document_types']
            invoice.journal_document_type_id = res['journal_document_type']

    @api.multi
    def write(self, vals):
        """
        If someone change the type (for eg from sale order), we update
        de document type
        """
        inv_type = vals.get('type')
        # if len(vals) == 1 and vals.get('type'):
        # podrian pasarse otras cosas ademas del type
        if inv_type:
            for rec in self:
                res = rec._get_available_journal_document_types(
                    rec.journal_id, inv_type, rec.partner_id)
                vals['journal_document_type_id'] = res[
                    'journal_document_type'].id
                # call write for each inoice
                super(AccountInvoice, rec).write(vals)
                return True
        return super(AccountInvoice, self).write(vals)

    @api.model
    def _get_available_journal_document_types(self, journal, invoice_type,
                                              partner):
        """
        This function is to be inherited by differents localizations and MUST
        return a dictionary with two keys:
        * 'available_journal_document_types': available document types on
        this invoice
        * 'journal_document_type': suggested document type on this invoice
        """
        # As default we return every journal document type, and if one exists
        # then we return the first one as suggested
        journal_document_types = journal.journal_document_type_ids
        # if invoice is a refund only show credit_notes, else, not credit note
        if invoice_type in ['out_refund', 'in_refund']:
            journal_document_types = journal_document_types.filtered(
                # lambda x: x.document_type_id.internal_type == 'credit_note')
                lambda x: x.document_type_id.internal_type in
                ['credit_note', 'in_document'])
        else:
            journal_document_types = journal_document_types.filtered(
                lambda x: x.document_type_id.internal_type != 'credit_note')
        journal_document_type = (journal_document_types
                                 and journal_document_types[0]
                                 or journal_document_types)
        return {
            'available_journal_document_types': journal_document_types,
            'journal_document_type': journal_document_type,
        }

    @api.multi
    @api.constrains('document_type_id', 'document_number')
    @api.onchange('document_type_id', 'document_number')
    def validate_document_number(self):
        for rec in self:
            # if we have a sequence, number is set by sequence and we dont
            # check this
            if rec.document_sequence_id:
                continue
            document_type = rec.document_type_id

            if rec.document_type_id:
                res = document_type.validate_document_number(
                    rec.document_number)
                if res and res != rec.document_number:
                    rec.document_number = res

    @api.multi
    @api.constrains('journal_document_type_id', 'journal_id')
    def check_journal_document_type_journal(self):
        for rec in self:
            if rec.journal_document_type_id and (
                    rec.journal_document_type_id.journal_id != rec.journal_id):
                raise Warning(
                    _('El Tipo de Documento elegido "%s" no pertenece al diario'
                      ' "%s". Por favor pruebe elegir otro tipo de documento.'
                      'Puede refrezcar los tipos de documentos disponibles '
                      'cambiando el diario o el partner.') %
                    ((rec.journal_document_type_id.display_name,
                      rec.journal_id.name)))

    @api.multi
    @api.constrains('type', 'document_type_id')
    def check_invoice_type_document_type(self):
        for rec in self:
            internal_type = rec.document_type_internal_type
            invoice_type = rec.type
            if not internal_type:
                continue
            elif internal_type in [
                    'debit_note', 'invoice'
            ] and invoice_type in ['out_refund', 'in_refund']:
                raise Warning(
                    _('You can not use a %s document type with a refund '
                      'invoice') % internal_type)
            elif internal_type == 'credit_note' and invoice_type in [
                    'out_invoice', 'in_invoice'
            ]:
                raise Warning(
                    _('You can not use a %s document type with a invoice') %
                    (internal_type))

    @api.model
    def _prepare_refund(self,
                        invoice,
                        date_invoice=None,
                        date=None,
                        description=None,
                        journal_id=None):
        values = super(AccountInvoice,
                       self)._prepare_refund(invoice,
                                             date_invoice=date_invoice,
                                             date=date,
                                             description=description,
                                             journal_id=journal_id)
        refund_journal_document_type_id = self._context.get(
            'refund_journal_document_type_id', False)
        refund_document_number = self._context.get('refund_document_number',
                                                   False)
        if refund_journal_document_type_id:
            values['journal_document_type_id'] = \
                refund_journal_document_type_id
        if refund_document_number:
            values['document_number'] = refund_document_number
        return values
Beispiel #18
0
class account_register_payments(models.TransientModel):
    _inherit = "account.register.payments"
    
    def _get_register_invoices(self):
        return self.env['account.invoice'].browse(self._context.get('active_ids'))
    
    def _get_register_lines(self, register_ids):
        registers = []
        if register_ids:
            for register in register_ids:
                registers.append(register.id)
        return self.env['account.register.line'].browse(registers)
    
    def _set_currency_rate(self):
        if self.is_force_curr:
            company_currency = self.journal_id.company_id.currency_id
            payment_currency = self.currency_id or company_currency
            self.force_rate = payment_currency.with_context(date=self.payment_date).compute(1.0, company_currency, round=False)
            self.company_currency_id = company_currency.id
        else:
            self.force_rate = 0.0
    
    company_currency_id = fields.Many2one('res.currency', string='Company Currency')
    is_force_curr = fields.Boolean('Kurs Nego')
    force_rate = fields.Monetary('Kurs Nego Amount')
    payment_adm = fields.Selection([
            ('cash','Cash'),
            ('free_transfer','Non Payment Administration Transfer'),
            ('transfer','Transfer'),
            #('check','Check/Giro'),
            #('letter','Letter Credit'),
            ('cc','Credit Card'),
            ('dc','Debit Card'),
            ],string='Payment Adm')
    card_number = fields.Char('Card Number', size=128, required=False)
    card_type = fields.Selection([
            ('visa','Visa'),
            ('master','Master'),
            ('bca','BCA Card'),
            ('citi','CITI Card'),
            ('amex','AMEX'),
            ], string='Card Type', size=128)
    register_ids = fields.One2many('account.register.line', 'register_id', copy=False, string='Register Invoice')
    
    @api.model
    def default_get(self, fields):
        rec = super(account_register_payments, self).default_get(fields)
        context = dict(self._context or {})
        active_model = context.get('active_model')
        active_ids = context.get('active_ids')
         
        reg_lines = []
        communication = []
        for invoice in self.env[active_model].browse(active_ids):
            if invoice.origin:
                name = invoice.number +':'+ invoice.origin
            else:
                name = invoice.number
            communication.append(name)
            reg_lines.append([0, 0, {
               'invoice_id': invoice.id,
               'name':  name,
               'amount_total': invoice.amount_total,
               'residual': invoice.residual,
               'amount_to_pay': invoice.residual,
               }])
        rec.update({
            'register_ids': reg_lines,
            'communication': ", ".join(communication),
        })
        return rec
    
    @api.onchange('journal_id', 'amount')
    def _onchange_journal_id(self):
        if self.journal_id:
            company_currency = self.journal_id.company_id.currency_id
            payment_currency = self.currency_id or company_currency
            if company_currency == payment_currency:
                self.is_force_curr = False
                self.force_rate = 0.0

    @api.onchange('is_force_curr','payment_date')
    def _onchange_is_force_curr(self):
        self._set_currency_rate()
    
    @api.onchange('register_ids')
    def _onchange_register_ids(self):
        amount = 0.0
        for line in self.register_ids:
            amount += line.amount_to_pay
        self.amount = amount
        return
    
    def get_payment_line_vals(self, payment, line):
        """ Hook for extension """
        return {
            'payment_id': payment.id,
            'name': line.name,
            'invoice_id': line.invoice_id.id,
            #'type': line.debit and 'dr' or 'cr',
            'amount_total': line.amount_total,
            'residual': line.residual,
            'amount_to_pay': line.amount_to_pay,
            'payment_difference': line.payment_difference,
            'payment_difference_handling': line.payment_difference_handling,
            'writeoff_account_id': line.writeoff_account_id and line.writeoff_account_id.id or False,
        }
    
    @api.multi
    def create_payment(self):
        payment = self.env['account.payment'].create(self.get_payment_vals())
        if payment:
            for line in self._get_register_lines(self.register_ids):
                self.env['account.payment.line'].create(self.get_payment_line_vals(payment, line))
            payment.write({'register_date': self.payment_date, 
                           'is_force_curr': self.is_force_curr, 
                           'force_rate': self.force_rate,
                           'payment_adm': self.payment_adm,
                           'card_number': self.card_number,
                           'card_type': self.card_type,
                           })
        payment.post_multi()
        return payment#{'type': 'ir.actions.act_window_close'}
Beispiel #19
0
class DonationDonation(models.Model):
    _name = 'donation.donation'
    _description = 'Donation'
    _order = 'id desc'
    _rec_name = 'display_name'
    _inherit = ['mail.thread']

    @api.multi
    @api.depends('line_ids', 'line_ids.unit_price', 'line_ids.quantity',
                 'line_ids.product_id', 'donation_date', 'currency_id',
                 'company_id')
    def _compute_total(self):
        for donation in self:
            total = tax_receipt_total = 0.0
            company_currency = donation.company_currency_id
            donation_currency = donation.currency_id
            # Do not consider other currencies for tax receipts
            # because, for the moment, only very very few countries
            # accept tax receipts from other countries, and never in another
            # currency. If you know such cases, please tell us and we will
            # update the code of this module
            for line in donation.line_ids:
                line_total = line.quantity * line.unit_price
                total += line_total
                if (donation_currency == company_currency
                        and line.product_id.tax_receipt_ok):
                    tax_receipt_total += line_total

            donation.amount_total = total
            donation_currency =\
                donation.currency_id.with_context(date=donation.donation_date)
            total_company_currency = donation_currency.compute(
                total, donation.company_id.currency_id)
            donation.amount_total_company_currency = total_company_currency
            donation.tax_receipt_total = tax_receipt_total

    # We don't want a depends on partner_id.country_id, because if the partner
    # moves to another country, we want to keep the old country for
    # past donations to have good statistics
    @api.multi
    @api.depends('partner_id')
    def _compute_country_id(self):
        # Use sudo() to by-pass record rules, because the same partner
        # can have donations in several companies
        for donation in self:
            donation.sudo().country_id = donation.partner_id.country_id

    @api.model
    def _default_currency(self):
        company = self.env['res.company']._company_default_get(
            'donation.donation')
        return company.currency_id

    @api.model
    def _get_default_requested_by(self):
        return self.env['res.users'].browse(self.env.uid)

    currency_id = fields.Many2one('res.currency',
                                  string='Currency',
                                  required=True,
                                  states={'done': [('readonly', True)]},
                                  track_visibility='onchange',
                                  ondelete='restrict',
                                  default=_default_currency)
    partner_id = fields.Many2one('res.partner',
                                 string='Donor',
                                 required=True,
                                 index=True,
                                 states={'done': [('readonly', True)]},
                                 track_visibility='onchange',
                                 ondelete='restrict')
    commercial_partner_id = fields.Many2one(
        related='partner_id.commercial_partner_id',
        string='Parent Donor',
        readonly=True,
        store=True,
        index=True)
    # country_id is here to have stats per country
    # WARNING : I can't put a related field, because when someone
    # writes on the country_id of a partner, it will trigger a write
    # on all it's donations, including donations in other companies
    # which will be blocked by the record rule
    country_id = fields.Many2one('res.country',
                                 string='Country',
                                 compute='_compute_country_id',
                                 store=True,
                                 readonly=True,
                                 copy=False)
    donation_by = fields.Many2one('res.users',
                                  'Donation by',
                                  required=True,
                                  track_visibility='onchange',
                                  default=_get_default_requested_by)
    donation_collector = fields.Many2one('hr.employee',
                                         string='Collector',
                                         track_visibility='onchange')
    donation_place = fields.Many2one('donation.place',
                                     'Donation Place',
                                     store=True)
    gov_id = fields.Many2one('govs.villages.gov',
                             related='donation_by.gov_id',
                             string='Gov',
                             store=True)
    check_total = fields.Monetary(string='Check Amount',
                                  digits=dp.get_precision('Account'),
                                  states={'done': [('readonly', True)]},
                                  currency_field='currency_id',
                                  track_visibility='onchange')
    amount_total = fields.Monetary(compute='_compute_total',
                                   string='Amount Total',
                                   currency_field='currency_id',
                                   store=True,
                                   digits=dp.get_precision('Account'),
                                   readonly=True)
    amount_total_company_currency = fields.Monetary(
        compute='_compute_total',
        string='Amount Total in Company Currency',
        currency_field='company_currency_id',
        store=True,
        digits=dp.get_precision('Account'),
        readonly=True)
    donation_date = fields.Date(string='Donation Date',
                                required=True,
                                default=fields.Date.context_today,
                                states={'done': [('readonly', True)]},
                                index=True,
                                track_visibility='onchange')
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 states={'done': [('readonly', True)]},
                                 default=lambda self: self.env['res.company'].
                                 _company_default_get('donation.donation'))
    line_ids = fields.One2many('donation.line',
                               'donation_id',
                               string='Donation Lines',
                               states={'done': [('readonly', True)]},
                               copy=True)
    move_id = fields.Many2one('account.move',
                              string='Account Move',
                              readonly=True,
                              copy=False)
    move_analytic_id = fields.Many2one('account.analytic.line',
                                       string='Account Analytic',
                                       readonly=True,
                                       copy=False)

    number = fields.Char(related='move_id.name',
                         readonly=True,
                         size=64,
                         store=True,
                         string='Donation Number')
    journal_id = fields.Many2one(
        'account.journal',
        string='Payment Method',
        required=True,
        domain=[('type', 'in', ('bank', 'cash')),
                ('allow_donation', '=', True)],
        states={'done': [('readonly', True)]},
        track_visibility='onchange',
        default=lambda self: self.env.user.context_donation_journal_id)
    payment_ref = fields.Char(string='Payment Reference',
                              size=32,
                              states={'done': [('readonly', True)]})
    state = fields.Selection([
        ('draft', 'Draft'),
        ('transfer', 'Transfer'),
        ('done', 'Done'),
        ('cancel', 'Cancelled'),
    ],
                             string='State',
                             readonly=True,
                             copy=False,
                             default='draft',
                             index=True,
                             track_visibility='onchange')
    company_currency_id = fields.Many2one(related='company_id.currency_id',
                                          string="Company Currency",
                                          readonly=True)
    campaign_id = fields.Many2one(
        'donation.campaign',
        string='Donation Campaign',
        track_visibility='onchange',
        ondelete='restrict',
        default=lambda self: self.env.user.context_donation_campaign_id)
    display_name = fields.Char(string='Display Name',
                               compute='_compute_display_name',
                               readonly=True)
    #display_tag = fields.Char(string ='Tag')
    tax_receipt_id = fields.Many2one('donation.tax.receipt',
                                     string='Tax Receipt',
                                     readonly=True,
                                     copy=False)
    tax_receipt_option = fields.Selection(
        [
            ('none', 'None'),
            ('each', 'For Each Donation'),
            ('annual', 'Annual Tax Receipt'),
        ],
        string='Tax Receipt Option',
        states={'done': [('readonly', True)]},
        index=True)
    tax_receipt_total = fields.Monetary(
        compute='_compute_total',
        string='Eligible Tax Receipt Sub-total',
        store=True,
        currency_field='company_currency_id')
    tags_id = fields.Many2many('account.analytic.tag',
                               string='Tags',
                               readonly=True)
    donation_method = fields.Many2one('donation.instrument',
                                      store=True,
                                      string='Donation Method')
    account_id = fields.Many2one('account.account',
                                 related='donation_place.account_id',
                                 store=True,
                                 string='Account',
                                 readonly=True)

    #analytic = fields.Integer(string="id",compute='get_analytic_account_id_2')
    #analytic_tag = fields.Integer(string="tegid",compute='get_analytic_account_id_2')

    @api.multi
    @api.constrains('donation_date')
    def _check_donation_date(self):
        for donation in self:
            if donation.donation_date > fields.Date.context_today(self):
                # TODO No error pop-up to user : Odoo 9 BUG ?
                raise ValidationError(
                    _('The date of the donation of %s should be today '
                      'or in the past, not in the future!') %
                    donation.partner_id.name)

    @api.multi
    def _prepare_each_tax_receipt(self):
        self.ensure_one()
        vals = {
            'company_id': self.company_id.id,
            'currency_id': self.company_currency_id.id,
            'donation_date': self.donation_date,
            'amount': self.tax_receipt_total,
            'type': 'each',
            'partner_id': self.commercial_partner_id.id,
        }
        return vals

    @api.model
    def _prepare_move_line_name(self):
        name = _('Donation of %s') % self.partner_id.name
        return name

    @api.multi
    def _prepare_counterpart_move_line(self, name, amount_total_company_cur,
                                       total_amount_currency, currency_id):
        self.ensure_one()
        precision = self.env['decimal.precision'].precision_get('Account')
        if float_compare(amount_total_company_cur,
                         0,
                         precision_digits=precision) == 1:
            debit = amount_total_company_cur
            credit = 0
            total_amount_currency = self.amount_total
        else:
            credit = amount_total_company_cur * -1
            debit = 0
            total_amount_currency = self.amount_total * -1
        vals = {
            'debit': debit,
            'credit': credit,
            'name': name,
            'account_id': self.journal_id.default_debit_account_id.id,
            'partner_id': self.commercial_partner_id.id,
            'currency_id': currency_id,
            'amount_currency': (currency_id and total_amount_currency or 0.0),
        }
        return vals

    @api.multi
    def _prepare_donation_move(self):
        self.ensure_one()
        if not self.journal_id.default_debit_account_id:
            raise UserError(
                _("Missing Default Debit Account on journal '%s'.") %
                self.journal_id.name)

        movelines = []
        if self.company_id.currency_id.id != self.currency_id.id:
            currency_id = self.currency_id.id
        else:
            currency_id = False
        # Note : we can have negative donations for donors that use direct
        # debit when their direct debit rejected by the bank
        amount_total_company_cur = 0.0
        total_amount_currency = 0.0
        name = self._prepare_move_line_name()
        aml = {}
        # key = (account_id, analytic_account_id)
        # value = {'credit': ..., 'debit': ..., 'amount_currency': ...}
        precision = self.env['decimal.precision'].precision_get('Account')
        for donation_line in self.line_ids:
            if donation_line.in_kind:
                continue
            amount_total_company_cur += donation_line.amount_company_currency
            #account_id = donation_line.product_id.property_account_income_id.id
            #if not account_id:
            #   account_id = donation_line.product_id.categ_id.\
            #      property_account_income_categ_id.id
            account_id = donation_line.get_account_id()
            if not account_id:
                raise UserError(
                    _("Missing income account on product '%s' or on it's "
                      "related product category") %
                    donation_line.product_id.name)
            analytic_account_id = donation_line.get_analytic_account_id()

            amount_currency = 0.0
            if float_compare(donation_line.amount_company_currency,
                             0,
                             precision_digits=precision) == 1:
                credit = donation_line.amount_company_currency
                debit = 0
                amount_currency = donation_line.amount * -1
            else:
                debit = donation_line.amount_company_currency * -1
                credit = 0
                amount_currency = donation_line.amount

            # TODO Take into account the option group_invoice_lines ?
            if (account_id, analytic_account_id) in aml:
                aml[(account_id, analytic_account_id)]['credit'] += credit
                aml[(account_id, analytic_account_id)]['debit'] += debit
                aml[(account_id, analytic_account_id)]['amount_currency'] \
                    += amount_currency
            else:
                aml[(account_id, analytic_account_id)] = {
                    'credit': credit,
                    'debit': debit,
                    'amount_currency': amount_currency,
                }

        if not aml:  # for full in-kind donation
            return False

        for (account_id, analytic_account_id), content in aml.iteritems():
            movelines.append((0, 0, {
                'name':
                name,
                'credit':
                content['credit'],
                'debit':
                content['debit'],
                'account_id':
                account_id,
                'analytic_account_id':
                analytic_account_id,
                'partner_id':
                self.commercial_partner_id.id,
                'currency_id':
                currency_id,
                'amount_currency': (currency_id and content['amount_currency']
                                    or 0.0),
            }))

        # counter-part
        ml_vals = self._prepare_counterpart_move_line(
            name, amount_total_company_cur, total_amount_currency, currency_id)
        movelines.append((0, 0, ml_vals))

        vals = {
            'journal_id': self.journal_id.id,
            'date': self.donation_date,
            'ref': self.payment_ref,
            'line_ids': movelines,
        }
        return vals

    @api.one
    def _prepare_analytic_line(self):
        """ Prepare the values used to create() an account.analytic.line upon validation of an account.move.line having
            an analytic account. This method is intended to be extended in other modules.
        """
        #for donation_line in self.line_ids:
        #if donation_line.in_kind:
        #continue
        #amount = (self.credit or 0.0) - (self.debit or 0.0)
        #account_id = self.line_ids.product_id.property_account_income_id.id

        #if not account_id:
        #   account_id = self.line_ids.product_id.categ_id.property_account_income_categ_id.id
        name = self._prepare_move_line_name()
        vals = {
            'name': name,
            'date': self.donation_date,
            'account_id': self.campaign_id.analytic_account_id.id,
            'partner_id': self.commercial_partner_id.id,
            #'unit_amount': self.quantity,
            #'product_id': self.product_id and self.product_id.id or False,
            #'product_uom_id': self.product_uom_id and self.product_uom_id.id or False,
            'unit_amount': False,
            'product_id': False,
            'product_uom_id': False,
            #'amount': self.company_currency_id.with_context(date=self.date or fields.Date.context_today(self)).compute(amount, self.analytic_account_id.currency_id) if self.analytic_account_id.currency_id else amount,
            'amount': self.amount_total,
            'general_account_id': self.donation_place.account_id.id,
            'ref': self.payment_ref,
            'move_id': self.move_id.id,
            'user_id': self._uid,
            'tag_ids': self.tags_id,
        }
        return vals

    @api.multi
    def transfer(self):
        for rec in self:
            rec.state = 'transfer'
            #self.env.cr.execute("SELECT place.tag_id FROM donation_donation inner join donation_place as place on donation_donation.donation_place = place.id where donation_donation.id= '%s'" %(self.id))
            #res = self.env.cr.fetchone()[0]
            #rec.env.cr.execute("insert INTO account_analytic_tag_donation_line_rel(donation_line_id, account_analytic_tag_id) VALUES ('%s','%s')" %(rec.line_ids.id,res))

            #self.env.cr.execute("SELECT gov.tag_id FROM donation_donation inner join govs_villages_gov as gov on donation_donation.gov_id = gov.id where donation_donation.id= '%s'" %(self.id))
            #res2 = self.env.cr.fetchone()[0]
            #rec.env.cr.execute("insert INTO account_analytic_tag_donation_line_rel(donation_line_id, account_analytic_tag_id) VALUES ('%s','%s')" %(rec.line_ids.id,res2))

            #self.env.cr.execute("SELECT method.tag_id FROM donation_line inner join donation_instrument as method on donation_line.donation_method = method.id where donation_line.id= '%s'" %(self.line_ids.id))
            #res3 = self.env.cr.fetchone()[0]
            #rec.env.cr.execute("insert INTO account_analytic_tag_donation_line_rel(donation_line_id, account_analytic_tag_id) VALUES ('%s','%s')" %(rec.line_ids.id,res3))

            self.env.cr.execute(
                "SELECT place.tag_id FROM donation_donation inner join donation_place as place on donation_donation.donation_place = place.id where donation_donation.id= '%s'"
                % (self.id))
            res11 = self.env.cr.fetchone()[0]
            rec.env.cr.execute(
                "insert INTO account_analytic_tag_donation_donation_rel(donation_donation_id, account_analytic_tag_id) VALUES ('%s','%s')"
                % (rec.id, res11))

            self.env.cr.execute(
                "SELECT gov.tag_id FROM donation_donation inner join govs_villages_gov as gov on donation_donation.gov_id = gov.id where donation_donation.id= '%s'"
                % (self.id))
            res12 = self.env.cr.fetchone()[0]
            rec.env.cr.execute(
                "insert INTO account_analytic_tag_donation_donation_rel(donation_donation_id, account_analytic_tag_id) VALUES ('%s','%s')"
                % (rec.id, res12))

            self.env.cr.execute(
                "SELECT method.tag_id FROM donation_donation inner join donation_instrument as method on donation_donation.donation_method = method.id where donation_donation.id= '%s'"
                % (self.id))
            res13 = self.env.cr.fetchone()[0]
            rec.env.cr.execute(
                "insert INTO account_analytic_tag_donation_donation_rel(donation_donation_id, account_analytic_tag_id) VALUES ('%s','%s')"
                % (rec.id, res13))

    @api.multi
    def validate(self):
        check_total = self.env['res.users'].has_group(
            'donation.group_donation_check_total')
        precision = self.env['decimal.precision'].precision_get('Account')
        for donation in self:
            if not donation.line_ids:
                raise UserError(
                    _("Cannot validate the donation of %s because it doesn't "
                      "have any lines!") % donation.partner_id.name)

            if float_is_zero(donation.amount_total,
                             precision_digits=precision):
                raise UserError(
                    _("Cannot validate the donation of %s because the "
                      "total amount is 0 !") % donation.partner_id.name)

            if donation.state != 'transfer':
                raise UserError(
                    _("Cannot validate the donation of %s because it is not "
                      "in transfer state.") % donation.partner_id.name)

            if check_total and donation.check_total != donation.amount_total:
                raise UserError(
                    _("The amount of the donation of %s (%s) is different "
                      "from the sum of the donation lines (%s).") %
                    (donation.partner_id.name, donation.check_total,
                     donation.amount_total))
            vals = {'state': 'done'}
            if donation.amount_total:
                move_vals = donation._prepare_donation_move()
                move_analytic_vals = donation._prepare_analytic_line()[0]
                # when we have a full in-kind donation: no account move
                if move_vals:
                    move = self.env['account.move'].create(move_vals)
                    #move.post()
                    move_id2 = move.id
                    vals['move_id'] = move.id
                    move_analytic = self.env['account.analytic.line'].create(
                        move_analytic_vals)
                    move_analytic2 = move_analytic.id
                    analytic = self.campaign_id.analytic_account_id.id
                    vals['move_analytic_id'] = move_analytic.id
                    self.env.cr.execute(
                        "SELECT id FROM account_move_line where move_id = '%s' and analytic_account_id ='%d'"
                        % (move.id, analytic))
                    res111 = self.env.cr.fetchone()[0]
                    #ress = (move.id*2) - 1
                    self.env.cr.execute(
                        "UPDATE account_analytic_line set move_id= '%s' where id= '%d'"
                        % (res111, move_analytic.id))

                    resss = self.account_id.id
                    self.env.cr.execute(
                        "UPDATE account_analytic_line set general_account_id= '%s' where id= '%d'"
                        % (resss, move_analytic.id))

                    ressss = self.commercial_partner_id.id
                    self.env.cr.execute(
                        "UPDATE account_analytic_line set partner_id= '%s' where id= '%d'"
                        % (ressss, move_analytic.id))

                    ress2 = move_analytic.id
                    self.env.cr.execute(
                        "SELECT place.tag_id FROM donation_donation inner join donation_place as place on donation_donation.donation_place = place.id where donation_donation.id= '%s'"
                        % (self.id))
                    res11 = self.env.cr.fetchone()[0]
                    self.env.cr.execute(
                        "insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')"
                        % (ress2, res11))

                    self.env.cr.execute(
                        "SELECT gov.tag_id FROM donation_donation inner join govs_villages_gov as gov on donation_donation.gov_id = gov.id where donation_donation.id= '%s'"
                        % (self.id))
                    res12 = self.env.cr.fetchone()[0]
                    self.env.cr.execute(
                        "insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')"
                        % (ress2, res12))

                    self.env.cr.execute(
                        "SELECT method.tag_id FROM donation_donation inner join donation_instrument as method on donation_donation.donation_method = method.id where donation_donation.id= '%s'"
                        % (self.id))
                    res13 = self.env.cr.fetchone()[0]
                    self.env.cr.execute(
                        "insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')"
                        % (ress2, res13))
                else:
                    donation.message_post(
                        _('Full in-kind donation: no account move generated'))
            if (donation.tax_receipt_option == 'each'
                    and donation.tax_receipt_total
                    and not donation.tax_receipt_id):
                receipt_vals = donation._prepare_each_tax_receipt()
                receipt = self.env['donation.tax.receipt'].create(receipt_vals)
                vals['tax_receipt_id'] = receipt.id
            donation.write(vals)
        return

    @api.multi
    def save_default_values(self):
        self.ensure_one()
        self.env.user.write({
            'context_donation_journal_id':
            self.journal_id.id,
            'context_donation_campaign_id':
            self.campaign_id.id,
        })

    #@api.multi
    #def analytic(self):
    #    for rec in self:
    #       ress = (self.move_id.id *2) - 1
    #      self.env.cr.execute("UPDATE account_analytic_line set move_id= '%s' where id= '%d'" %(ress,self.move_analytic_id.id))

    #     ress2 = self.move_analytic_id.id
    #    self.env.cr.execute("SELECT place.tag_id FROM donation_donation inner join donation_place as place on donation_donation.donation_place = place.id where donation_donation.id= '%s'" %(self.id))
    #   res11 = self.env.cr.fetchone()[0]
    #  rec.env.cr.execute("insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')" %(ress2,res11))

    # self.env.cr.execute("SELECT gov.tag_id FROM donation_donation inner join govs_villages_gov as gov on donation_donation.gov_id = gov.id where donation_donation.id= '%s'" %(self.id))
    #res12 = self.env.cr.fetchone()[0]
    #rec.env.cr.execute("insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')" %(ress2,res12))

    #self.env.cr.execute("SELECT method.tag_id FROM donation_donation inner join donation_instrument as method on donation_donation.donation_method = method.id where donation_donation.id= '%s'" %(self.id))
    #res13 = self.env.cr.fetchone()[0]
    #rec.env.cr.execute("insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')" %(ress2,res13))

    #self.env.cr.execute("SELECT place.tag_id FROM donation_donation inner join donation_place as place on donation_donation.donation_place = place.id where donation_donation.id= '%s'" %(self.id))
    #res11 = self.env.cr.fetchone()[0]
    #rec.env.cr.execute("insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')" %(ress,res11))

    #self.env.cr.execute("SELECT gov.tag_id FROM donation_donation inner join govs_villages_gov as gov on donation_donation.gov_id = gov.id where donation_donation.id= '%s'" %(self.id))
    #res12 = self.env.cr.fetchone()[0]
    #rec.env.cr.execute("insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')" %(ress,res12))

    #self.env.cr.execute("SELECT method.tag_id FROM donation_donation inner join donation_instrument as method on donation_donation.donation_method = method.id where donation_donation.id= '%s'" %(self.id))
    #res13 = self.env.cr.fetchone()[0]
    #rec.env.cr.execute("insert INTO account_analytic_line_tag_rel(line_id, tag_id) VALUES ('%s','%s')" %(ress,res13))
    @api.multi
    def done2cancel(self):
        '''from Done state to Cancel state'''
        for donation in self:
            if donation.tax_receipt_id:
                raise UserError(
                    _("You cannot cancel this donation because "
                      "it is linked to the tax receipt %s. You should first "
                      "delete this tax receipt (but it may not be legally "
                      "allowed).") % donation.tax_receipt_id.number)
            if donation.move_id:
                donation.move_id.button_cancel()
                donation.move_id.unlink()
            donation.state = 'cancel'

    @api.multi
    def cancel2draft(self):
        '''from Cancel state to Draft state'''
        for donation in self:
            if donation.move_id:
                raise UserError(
                    _("A cancelled donation should not be linked to "
                      "an account move"))
            if donation.tax_receipt_id:
                raise UserError(
                    _("A cancelled donation should not be linked to "
                      "a tax receipt"))
            donation.state = 'draft'

    @api.multi
    def unlink(self):
        for donation in self:
            if donation.state == 'done':
                raise UserError(
                    _("The donation '%s' is in Done state, so you cannot "
                      "delete it.") % donation.display_name)
            if donation.move_id:
                raise UserError(
                    _("The donation '%s' is linked to an account move, "
                      "so you cannot delete it.") % donation.display_name)
            if donation.tax_receipt_id:
                raise UserError(
                    _("The donation '%s' is linked to the tax receipt %s, "
                      "so you cannot delete it.") %
                    (donation.display_name, donation.tax_receipt_id.number))
        return super(DonationDonation, self).unlink()

    @api.multi
    @api.depends('state', 'partner_id', 'move_id')
    def _compute_display_name(self):
        for donation in self:
            if donation.state == 'draft':
                name = _('Draft Donation of %s') % donation.partner_id.name
            elif donation.state == 'cancel':
                name = _('Cancelled Donation of %s') % donation.partner_id.name
            else:
                name = donation.number
            donation.display_name = name

    @api.onchange('partner_id')
    def partner_id_change(self):
        if self.partner_id:
            self.tax_receipt_option = self.partner_id.tax_receipt_option

    @api.onchange('tax_receipt_option')
    def tax_receipt_option_change(self):
        res = {}
        if (self.partner_id and self.partner_id.tax_receipt_option == 'annual'
                and self.tax_receipt_option != 'annual'):
            res = {
                'warning': {
                    'title':
                    _('Error:'),
                    'message':
                    _('You cannot change the Tax Receipt '
                      'Option when it is Annual.'),
                },
            }
            self.tax_receipt_option = 'annual'
        return res
Beispiel #20
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, self.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

    _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)])
    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\' when voucher is in Pro-forma status,voucher does not have an 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_now')
    date_due = fields.Date('Due Date', readonly=True, select=True, states={'draft': [('readonly', False)]})
    audit = fields.Boolean('To Review', related="move_id.to_check", help='Check this box if you are unsure of that journal entry and if you want to note it as \'to be reviewed\' by an accounting expert.')

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        if self.journal_id.type == 'sale':
            account_id = self.partner_id.property_account_receivable_id.id
        elif self.journal_id.type == 'purchase':
            account_id = self.partner_id.property_account_payable_id.id
        elif self.voucher_type  == 'sale':
            account_id = sef.journal_id.default_debit_account_id.id
        elif self.voucher_type == 'purchase':
            account_id = self.journal_id.default_credit_account_id.id
        else:
            account_id = self.journal_id.default_credit_account_id.id or self.journal_id.default_debit_account_id.id
        self.account_id = 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.onchange('pay_now')
    def onchange_payment(self):
        account_id = False
        if self.pay_now == 'pay_later':
            partner = self.partner_id
            journal = self.journal_id
            if journal.type == 'sale':
                account_id = partner.property_account_receivable_id.id
            elif journal.type == 'purchase':
                account_id = partner.property_account_payable_id.id
            elif self.voucher_type == 'sale':
                account_id = journal.default_debit_account_id.id
            elif self.voucher_type == 'purchase':
                account_id = journal.default_credit_account_id.id
            else:
                account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
        self.account_id = account_id

    @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)
        '''
        prec = self.company_id.currency_id.rounding
        for line in self.line_ids:
            #create one move line per voucher line where amount is not 0.0
            # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
            if not line.price_subtotal and not (line.move_line_id and not float_compare(line.move_line_id.debit, line.move_line_id.credit, precision_digits=prec) and not float_compare(line.move_line_id.debit, 0.0, precision_digits=prec)):
                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)
Beispiel #21
0
class AccountInvoice(models.Model):
    _inherit = 'account.invoice'

    # no gravado en iva
    # cc_vat_untaxed = fields.Monetary(
    cc_vat_untaxed_base_amount = fields.Monetary(
        compute="_get_currency_values",
        string='Company Cur. VAT Untaxed',
    )
    # company currency default odoo fields
    cc_amount_total = fields.Monetary(
        compute="_get_currency_values",
        string='Company Cur. Total',
    )
    cc_amount_untaxed = fields.Monetary(
        compute="_get_currency_values",
        string='Company Cur. Untaxed',
    )
    cc_amount_tax = fields.Monetary(
        compute="_get_currency_values",
        string='Company Cur. Tax',
    )
    # von iva
    cc_vat_amount = fields.Monetary(
        compute="_get_currency_values",
        string='Company Cur. VAT Amount',
    )
    cc_other_taxes_amount = fields.Monetary(
        compute="_get_currency_values",
        string='Company Cur. Other Taxes Amount')

    @api.one
    @api.depends('currency_id')
    def _get_currency_values(self):
        # TODO si traer el rate de esta manera no resulta (por ej. porque
        # borran una linea de rate), entonces podemos hacerlo desde el move
        # mas o menos como hace account_invoice_currency o viendo el total de
        # debito o credito de ese mismo
        currency = self.currency_id.with_context(
            date=self.date_invoice or fields.Date.context_today(self))
        if not currency:
            return False
        if self.company_id.currency_id == currency:
            self.cc_amount_untaxed = self.amount_untaxed
            self.cc_amount_tax = self.amount_tax
            self.cc_amount_total = self.amount_total
            self.cc_vat_untaxed_base_amount = self.vat_untaxed_base_amount
            self.cc_vat_amount = self.vat_amount
            self.cc_other_taxes_amount = self.other_taxes_amount
            # self.currency_rate = 1.0
        else:
            currency_rate = currency.compute(1.0,
                                             self.company_id.currency_id,
                                             round=False)
            # otra alternativa serua usar currency.compute con round true
            # para cada uno de estos valores
            # self.currency_rate = currency_rate
            self.cc_amount_untaxed = currency.round(self.amount_untaxed *
                                                    currency_rate)
            self.cc_amount_tax = currency.round(self.amount_tax *
                                                currency_rate)
            self.cc_amount_total = currency.round(self.amount_total *
                                                  currency_rate)
            self.cc_vat_untaxed_base_amount = currency.round(
                self.vat_untaxed_base_amount * currency_rate)
            self.cc_vat_amount = currency.round(self.vat_amount *
                                                currency_rate)
            self.cc_other_taxes_amount = currency.round(
                self.other_taxes_amount * currency_rate)
Beispiel #22
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)
    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_id = fields.Many2one('account.account', 'Account', required=True, domain=[('deprecated', '=', False)])
    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': {}}
class AccountMoveLine(models.Model):
    _inherit = 'account.move.line'

    statement_line_id = fields.Many2one('account.bank.statement.line',
                                        related='move_id.statement_line_id',
                                        string='Bank Statement Line')
    other_balance = fields.Monetary(string='Other Balance', default=0.0)

    @api.model
    def create(self, vals):
        debit = vals.get('debit', 0)
        credit = vals.get('credit', 0)
        vals.update({'other_balance': credit - debit})
        return super(AccountMoveLine, self).create(vals)

    @api.multi
    def write(self, vals):
        self.calculate_orther_balance(vals)
        return super(AccountMoveLine, self).write(vals)

    @api.multi
    def calculate_orther_balance(self, vals):
        for record in self:
            if 'debit' in vals or 'credit' in vals:
                debit = vals.get('debit', record.debit)
                credit = vals.get('credit', record.credit)
                vals.update({
                    'other_balance': credit - debit,
                })
        return True

    @api.multi
    @api.constrains('move_id', 'account_id')
    def check_account_type_bank(self):
        for line in self:
            if line.move_id:
                line_account_bank = line.move_id.line_ids.filtered(
                    lambda a: a.account_id.reconciled_account)
                if len(line_account_bank) > 1:
                    raise UserError(
                        _('Only one journal item on an account requiring ' +
                          'bank reconciliation can be booked in this ' +
                          'account move. It is impossible to add another ' +
                          'one. Please create a distinct account move to ' +
                          'registrer this account.move.line and its ' +
                          'counterpart.'))

    def _create_writeoff(self, vals):
        res = super(AccountMoveLine, self)._create_writeoff(vals)
        partner = self.mapped('partner_id')
        lines = res.mapped('move_id.line_ids')
        for line in lines:
            line.partner_id = partner.id if len(partner) == 1 and\
                not any(not line.partner_id for line in self)\
                else False
        return res

    @api.multi
    def unmatch_bankstatement_wizard(self):
        active_ids = self._context.get('active_ids', [])
        active_model = self._context.get('active_model', [])

        view_id = self.env.ref(
            'coop_account.view_unmatch_bank_statement_wizard_form')

        mess_confirm = _('Are you sure you want to unmatch %s transactions?') %\
            (len(active_ids))

        wizard = self.env['unmatch.bank.statement.wizard'].create(
            {'mess_confirm': mess_confirm})

        return {
            'name': _('Unmatch Bank Statement'),
            'type': 'ir.actions.act_window',
            'view_id': view_id.id,
            'view_mode': 'form',
            'view_type': 'form',
            'res_id': wizard.id,
            'res_model': 'unmatch.bank.statement.wizard',
            'target': 'new',
            'context': {
                'active_ids': active_ids,
                'active_model': active_model
            }
        }

    @api.model
    def export_wrong_reconciliation_ml(self):
        wrong_reconciliation_ml_data = self.get_wrong_reconciliation_ml_data()
        data = {
            'model': self._name,
            'headers': wrong_reconciliation_ml_data['headers'],
            'rows': wrong_reconciliation_ml_data['rows']
        }
        return json.dumps(data)

    @api.model
    def get_wrong_reconciliation_ml_data(self):
        read_columns = [
            'name', 'journal_id', 'ref', 'date', 'partner_id', 'company_id',
            'check_holder_name', 'account_id', 'move_id', 'debit', 'credit',
            'quantity', 'statement_line_id', 'statement_id', 'payment_id',
            'check_deposit_id'
        ]
        model_fields = self._fields
        header_columns = [
            getattr(model_fields[column], 'string') for column in read_columns
        ]
        header_columns = ['External ID'] + header_columns
        read_columns = ['id'] + read_columns
        wrong_move_lines = self.get_wrong_reconciliation_ml()
        if not wrong_move_lines:
            raise Warning(_('Found no wrong account move lines.'))
        record_values_dict = wrong_move_lines.export_data(read_columns)
        converted_data_rows = [[unicode(cell_data) for cell_data in data_row]
                               for data_row in record_values_dict['datas']]
        return {
            'rows': converted_data_rows,
            'headers': header_columns,
        }

    @api.model
    def get_wrong_reconciliation_ml(self):
        wrong_move_lines = self
        selected_journals = self.env['account.journal'].search([
            ('name', 'not like', '%Chèques%'),
            '|',
            ('name', 'like', 'CCOOP - compte courant'),
            ('name', 'ilike', '%cep%'),
        ])
        bank_statement_line_query = """
            select statement_line_id
            from account_move
            where statement_line_id is not null and journal_id in {} 
            group by statement_line_id
            having count(statement_line_id) > 1
        """.format(tuple(selected_journals.ids))

        self.env.cr.execute(bank_statement_line_query)
        results = self.env.cr.fetchall()
        line_ids = [id_tuple[0] for id_tuple in results]
        bank_statement_line_ids = self.env['account.bank.statement.line']\
            .browse(line_ids)
        for stml in bank_statement_line_ids:
            stml_date = fields.Date.from_string(stml.date)
            move_ids = stml.journal_entry_ids
            move_line_ids = move_ids.mapped('line_ids')
            if len(move_ids) == 1 and len(move_line_ids) == 2:
                continue
            for aml in move_line_ids.filtered(lambda ml: not ml.statement_id):
                aml_date = fields.Date.from_string(aml.date)
                if aml_date.year <= stml_date.year and \
                        aml_date.month <= stml_date.month:
                    continue
                else:
                    wrong_move_lines |= aml
        return wrong_move_lines

    @api.model
    def run_reconcile_411_pos(self, nb_lines_per_job=100):
        # Prepare session for job
        session = ConnectorSession(self._cr,
                                   self._uid,
                                   context=self.env.context)
        parameter_obj = self.env['ir.config_parameter']
        reconcile_pos_account = parameter_obj.get_param(
            'to.reconcile.pos.account', False)
        reconcile_pos_account = self.env['account.account'].search(
            [('code', '=', reconcile_pos_account)], limit=1)
        if not reconcile_pos_account:
            _logger.warn(
                "Couldn't find account with code %s,"
                "please set value for key 'to.reconcile.pos.account'"
                " in config_parameter", reconcile_pos_account)
            return True

        debit_moves_domain = [
            ('reconciled', '=', False),
            ('account_id', '=',
             reconcile_pos_account and reconcile_pos_account.id or False),
            ('partner_id', '!=', None), ('debit', '>', 0), ('credit', '=', 0)
        ]
        # Create jobs
        lines = self.search(debit_moves_domain, order='id')

        # Reconcile the journal items by partner
        partner_ids = list(set(lines.mapped('partner_id.id')))
        total_lines = len(partner_ids)
        job_lines = nb_lines_per_job

        number_of_jobs = int(total_lines / job_lines) + \
            (total_lines % job_lines > 0)
        start_line = 0
        for i in range(1, number_of_jobs + 1):
            start_line = i * job_lines - job_lines
            end_line = i * job_lines
            chunk_ids = partner_ids[start_line:end_line]
            if i == number_of_jobs:
                chunk_ids = partner_ids[start_line:]

            session = ConnectorSession(self._cr,
                                       self._uid,
                                       context=self.env.context)
            job_reconcile_411_pos_by_ref.delay(session, 'res.partner',
                                               chunk_ids,
                                               reconcile_pos_account.id)
Beispiel #24
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 usablity 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
Beispiel #25
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')
    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)
    partner_id = fields.Many2one('res.partner', string='Customer')

    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 []
        recs = self.search([
            '|', '|', ('code', operator, name), ('partner_id', operator, name),
            ('name', operator, name)
        ] + args,
                           limit=limit)
        return recs.name_get()

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'open':
            return 'analytic.mt_account_opened'
        elif 'state' in init_values and self.state == 'close':
            return 'analytic.mt_account_closed'
        elif 'state' in init_values and self.state == 'pending':
            return 'analytic.mt_account_pending'
        return super(account_analytic_account,
                     self)._track_subtype(init_values)
Beispiel #26
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)
class PurchaseSuggest(models.TransientModel):
    _name = 'purchase.suggest'
    _description = 'Purchase Suggestions'
    _rec_name = 'product_id'

    # campos y métodos agregados agregados
    replenishment_cost = fields.Float(
        readonly=True,
    )
    currency_id = fields.Many2one(
        'res.currency',
        readonly=True,
    )
    virtual_available = fields.Float(
        string='Forecasted Quantity',
        digits=dp.get_precision('Product Unit of Measure'),
        readonly=True,
        help="Forecast quantity in the unit of measure of the product "
        "(computed as Quantity On Hand - Outgoing + Incoming + Draft PO "
        "quantity)"
    )
    rotation = fields.Float(
        readonly=True,
    )
    location_rotation = fields.Float(
        readonly=True,
    )
    order_amount = fields.Monetary(
        string='Order Amount',
        compute='_compute_order_amount',
        store=True,
    )
    qty_multiple = fields.Float(
        string="Qty Multiple", readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="in the unit of measure for the product"
    )

    @api.multi
    @api.depends('qty_to_order', 'replenishment_cost')
    def _compute_order_amount(self):
        for rec in self:
            rec.order_amount = rec.replenishment_cost * rec.qty_to_order

    @api.multi
    def action_traceability(self):
        self.ensure_one()
        action = self.env.ref('stock.act_product_stock_move_open')
        if action:
            action_read = action.read()[0]
            # nos da error al querer leerlo como dict
            # context = literal_eval(action_read['context'])
            # context['search_default_product_id'] = self.product_id.id
            # context['default_product_id'] = self.product_id.id
            context = {
                'search_default_future': 1,
                'search_default_picking_type': 1,
                'search_default_product_id': self.product_id.id,
                'default_product_id': self.product_id.id}
            action_read['context'] = context
            return action_read

    # campos originales de purchase suggest
    company_id = fields.Many2one(
        'res.company', string='Company', required=True)
    product_id = fields.Many2one(
        'product.product', string='Product', required=True, readonly=True)
    uom_id = fields.Many2one(
        'product.uom', string='UoM', related='product_id.uom_id',
        readonly=True)
    uom_po_id = fields.Many2one(
        'product.uom', string='Purchase UoM', related='product_id.uom_po_id',
        readonly=True)
    seller_id = fields.Many2one(
        'res.partner', string='Main Supplier', readonly=True,
        domain=[('supplier', '=', True)])
    qty_available = fields.Float(
        string='Quantity On Hand', readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="in the unit of measure of the product")
    incoming_qty = fields.Float(
        string='Incoming Quantity', readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="in the unit of measure of the product")
    outgoing_qty = fields.Float(
        string='Outgoing Quantity', readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="in the unit of measure of the product")
    draft_po_qty = fields.Float(
        string='Draft PO Quantity', readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="Draft purchase order quantity in the unit of measure "
        "of the product (NOT in the purchase unit of measure !)")
    last_po_line_id = fields.Many2one(
        'purchase.order.line', string='Last Purchase Order Line',
        readonly=True)
    last_po_date = fields.Datetime(
        related='last_po_line_id.order_id.date_order',
        string='Date of the Last Order', readonly=True)
    last_po_qty = fields.Float(
        related='last_po_line_id.product_qty', readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        string='Quantity of the Last Order')
    last_po_uom = fields.Many2one(
        related='last_po_line_id.product_uom', readonly=True,
        string='UoM of the Last Order')
    orderpoint_id = fields.Many2one(
        'stock.warehouse.orderpoint', string='Re-ordering Rule',
        readonly=True)
    location_id = fields.Many2one(
        'stock.location', string='Stock Location', readonly=True)
    min_qty = fields.Float(
        string="Min Quantity", readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="in the unit of measure for the product")
    max_qty = fields.Float(
        string="Max Quantity", readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="in the unit of measure for the product")
    qty_to_order = fields.Float(
        string='Quantity to Order',
        digits=dp.get_precision('Product Unit of Measure'),
        help="Quantity to order in the purchase unit of measure for the "
        "product")
class AccountBankStatementLine(models.Model):
    _name = "account.bank.statement.line"
    _description = "Bank Statement Line"
    _order = "statement_id desc, sequence"
    _inherit = ['ir.needaction_mixin']

    name = fields.Char(string='Memo', required=True)
    date = fields.Date(required=True, default=lambda self: self._context.get('date', fields.Date.context_today(self)))
    amount = fields.Monetary(digits=0, currency_field='journal_currency_id')
    journal_currency_id = fields.Many2one('res.currency', related='statement_id.currency_id',
        help='Utility field to express amount currency', readonly=True)
    partner_id = fields.Many2one('res.partner', string='Partner')
    bank_account_id = fields.Many2one('res.partner.bank', string='Bank Account')
    account_id = fields.Many2one('account.account', string='Counterpart Account', domain=[('deprecated', '=', False)],
        help="This technical field can be used at the statement line creation/import time in order to avoid the reconciliation"
             " process on it later on. The statement line will simply create a counterpart on this account")
    statement_id = fields.Many2one('account.bank.statement', string='Statement', index=True, required=True, ondelete='cascade')
    journal_id = fields.Many2one('account.journal', related='statement_id.journal_id', string='Journal', store=True, readonly=True)
    partner_name = fields.Char(help="This field is used to record the third party name when importing bank statement in electronic format,"
             " when the partner doesn't exist yet in the database (or cannot be found).")
    ref = fields.Char(string='Reference')
    note = fields.Text(string='Notes')
    sequence = fields.Integer(index=True, help="Gives the sequence order when displaying a list of bank statement lines.", default=1)
    company_id = fields.Many2one('res.company', related='statement_id.company_id', string='Company', store=True, readonly=True)
    journal_entry_ids = fields.One2many('account.move', 'statement_line_id', 'Journal Entries', copy=False, readonly=True)
    amount_currency = fields.Monetary(help="The amount expressed in an optional other currency if it is a multi-currency entry.")
    currency_id = fields.Many2one('res.currency', string='Currency', help="The optional other currency if it is a multi-currency entry.")

    @api.one
    @api.constrains('amount')
    def _check_amount(self):
        # This constraint could possibly underline flaws in bank statement import (eg. inability to
        # support hacks such as using dummy transactions to give additional informations)
        if self.amount == 0:
            raise ValidationError(_('A transaction can\'t have a 0 amount.'))

    @api.one
    @api.constrains('amount', 'amount_currency')
    def _check_amount_currency(self):
        if self.amount_currency != 0 and self.amount == 0:
            raise ValidationError(_('If "Amount Currency" is specified, then "Amount" must be as well.'))

    @api.multi
    def unlink(self):
        for line in self:
            if line.journal_entry_ids.ids:
                raise UserError(_('In order to delete a bank statement line, you must first cancel it to delete related journal items.'))
        return super(AccountBankStatementLine, self).unlink()

    @api.model
    def _needaction_domain_get(self):
        return [('journal_entry_ids', '=', False), ('account_id', '=', False)]

    @api.multi
    def button_cancel_reconciliation(self):
        # TOCKECK : might not behave as expected in case of reconciliations (match statement line with already
        # registered payment) or partial reconciliations : it will completely remove the existing payment.
        move_recs = self.env['account.move']
        for st_line in self:
            move_recs = (move_recs | st_line.journal_entry_ids)
        if move_recs:
            for move in move_recs:
                move.line_ids.remove_move_reconcile()
            move_recs.write({'statement_line_id': False})
            move_recs.button_cancel()
            move_recs.unlink()

    ####################################################
    # Reconciliation interface methods
    ####################################################

    @api.multi
    def get_data_for_reconciliation_widget(self, excluded_ids=None):
        """ Returns the data required to display a reconciliation widget, for each statement line in self """
        excluded_ids = excluded_ids or []
        ret = []

        for st_line in self:
            aml_recs = st_line.get_reconciliation_proposition(excluded_ids=excluded_ids)
            target_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.journal_id.company_id.currency_id
            rp = aml_recs.prepare_move_lines_for_reconciliation_widget(target_currency=target_currency, target_date=st_line.date)
            excluded_ids += [move_line['id'] for move_line in rp]
            ret.append({
                'st_line': st_line.get_statement_line_for_reconciliation_widget(),
                'reconciliation_proposition': rp
            })

        return ret

    def get_statement_line_for_reconciliation_widget(self):
        """ Returns the data required by the bank statement reconciliation widget to display a statement line """
        statement_currency = self.journal_id.currency_id or self.journal_id.company_id.currency_id
        if self.amount_currency and self.currency_id:
            amount = self.amount_currency
            amount_currency = self.amount
            amount_currency_str = amount_currency > 0 and amount_currency or -amount_currency
            amount_currency_str = formatLang(self.env, amount_currency_str, currency_obj=statement_currency)
        else:
            amount = self.amount
            amount_currency_str = ""
        amount_str = formatLang(self.env, abs(amount), currency_obj=self.currency_id or statement_currency)

        data = {
            'id': self.id,
            'ref': self.ref,
            'note': self.note or "",
            'name': self.name,
            'date': self.date,
            'amount': amount,
            'amount_str': amount_str,  # Amount in the statement line currency
            'currency_id': self.currency_id.id or statement_currency.id,
            'partner_id': self.partner_id.id,
            'journal_id': self.journal_id.id,
            'statement_id': self.statement_id.id,
            'account_code': self.journal_id.default_debit_account_id.code,
            'account_name': self.journal_id.default_debit_account_id.name,
            'partner_name': self.partner_id.name,
            'communication_partner_name': self.partner_name,
            'amount_currency_str': amount_currency_str,  # Amount in the statement currency
            'has_no_partner': not self.partner_id.id,
        }
        if self.partner_id:
            if amount > 0:
                data['open_balance_account_id'] = self.partner_id.property_account_receivable_id.id
            else:
                data['open_balance_account_id'] = self.partner_id.property_account_payable_id.id

        return data

    @api.multi
    def get_move_lines_for_reconciliation_widget(self, excluded_ids=None, str=False, offset=0, limit=None):
        """ Returns move lines for the bank statement reconciliation widget, formatted as a list of dicts
        """
        aml_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, str=str, offset=offset, limit=limit)
        target_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id
        return aml_recs.prepare_move_lines_for_reconciliation_widget(target_currency=target_currency, target_date=self.date)

    ####################################################
    # Reconciliation methods
    ####################################################

    def get_move_lines_for_reconciliation(self, excluded_ids=None, str=False, offset=0, limit=None, additional_domain=None, overlook_partner=False):
        """ Return account.move.line records which can be used for bank statement reconciliation.

            :param excluded_ids:
            :param str:
            :param offset:
            :param limit:
            :param additional_domain:
            :param overlook_partner:
        """
        # Blue lines = payment on bank account not assigned to a statement yet
        reconciliation_aml_accounts = [self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id]
        domain_reconciliation = ['&', '&', ('statement_id', '=', False), ('account_id', 'in', reconciliation_aml_accounts), ('payment_id','<>', False)]

        # Black lines = unreconciled & (not linked to a payment or open balance created by statement
        domain_matching = ['&', ('reconciled', '=', False), '|', ('payment_id','=',False), ('statement_id', '<>', False)]
        if self.partner_id.id or overlook_partner:
            domain_matching = expression.AND([domain_matching, [('account_id.internal_type', 'in', ['payable', 'receivable'])]])
        else:
            # TODO : find out what use case this permits (match a check payment, registered on a journal whose account type is other instead of liquidity)
            domain_matching = expression.AND([domain_matching, [('account_id.reconcile', '=', True)]])

        # Let's add what applies to both
        domain = expression.OR([domain_reconciliation, domain_matching])
        if self.partner_id.id and not overlook_partner:
            domain = expression.AND([domain, [('partner_id', '=', self.partner_id.id)]])

        # Domain factorized for all reconciliation use cases
        ctx = dict(self._context or {})
        ctx['bank_statement_line'] = self
        generic_domain = self.env['account.move.line'].with_context(ctx).domain_move_lines_for_reconciliation(excluded_ids=excluded_ids, str=str)
        domain = expression.AND([domain, generic_domain])

        # Domain from caller
        if additional_domain is None:
            additional_domain = []
        else:
            additional_domain = expression.normalize_domain(additional_domain)
        domain = expression.AND([domain, additional_domain])

        return self.env['account.move.line'].search(domain, offset=offset, limit=limit, order="date_maturity asc, id asc")

    def _get_domain_maker_move_line_amount(self):
        """ Returns a function that can create the appropriate domain to search on move.line amount based on statement.line currency/amount """
        company_currency = self.journal_id.company_id.currency_id
        st_line_currency = self.currency_id or self.journal_id.currency_id
        currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
        field = currency and 'amount_residual_currency' or 'amount_residual'
        precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places

        def ret(comparator, amount, p=precision, f=field, c=currency):
            if comparator == '<':
                if amount < 0:
                    domain = [(f, '<', 0), (f, '>', amount)]
                else:
                    domain = [(f, '>', 0), (f, '<', amount)]
            elif comparator == '=':
                domain = [(f, '=', float_round(amount, precision_digits=p))]
            else:
                raise UserError(_("Programmation error : domain_maker_move_line_amount requires comparator '=' or '<'"))
            domain += [('currency_id', '=', c)]
            return domain

        return ret

    def get_reconciliation_proposition(self, excluded_ids=None):
        """ Returns move lines that constitute the best guess to reconcile a statement line
            Note: it only looks for move lines in the same currency as the statement line.
        """
        # Look for structured communication match
        if self.name:
            overlook_partner = not self.partner_id  # If the transaction has no partner, look for match in payable and receivable account anyway
            domain = [('ref', '=', self.name)]
            match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=2, additional_domain=domain, overlook_partner=overlook_partner)
            if match_recs and len(match_recs) == 1:
                return match_recs
            elif len(match_recs) == 0:
                move = self.env['account.move'].search([('name', '=', self.name)], limit=1)
                if move:
                    domain = [('move_id', '=', move.id)]
                    match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=2, additional_domain=domain, overlook_partner=overlook_partner)
                    if match_recs and len(match_recs) == 1:
                        return match_recs

        # How to compare statement line amount and move lines amount
        amount_domain_maker = self._get_domain_maker_move_line_amount()
        amount = self.amount_currency or self.amount

        # Look for a single move line with the same amount
        match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=1, additional_domain=amount_domain_maker('=', amount))
        if match_recs:
            return match_recs

        if not self.partner_id:
            return self.env['account.move.line']

        # Select move lines until their total amount is greater than the statement line amount
        domain = [('reconciled', '=', False)]
        domain += [('account_id.user_type_id.type', '=', amount > 0 and 'receivable' or 'payable')]  # Make sure we can't mix receivable and payable
        domain += amount_domain_maker('<', amount)  # Will also enforce > 0
        mv_lines = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=5, additional_domain=domain)
        st_line_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id
        ret = self.env['account.move.line']
        total = 0
        for line in mv_lines:
            total += line.currency_id and line.amount_residual_currency or line.amount_residual
            if float_compare(total, abs(amount), precision_digits=st_line_currency.rounding) != -1:
                break
            ret = (ret | line)
        return ret

    def _get_move_lines_for_auto_reconcile(self):
        """ Returns the move lines that the method auto_reconcile can use to try to reconcile the statement line """
        pass

    @api.multi
    def auto_reconcile(self):
        """ Try to automatically reconcile the statement.line ; return the counterpart journal entry/ies if the automatic reconciliation succeeded, False otherwise.
            TODO : this method could be greatly improved and made extensible
        """
        self.ensure_one()
        match_recs = self.env['account.move.line']

        # How to compare statement line amount and move lines amount
        amount_domain_maker = self._get_domain_maker_move_line_amount()
        equal_amount_domain = amount_domain_maker('=', self.amount_currency or self.amount)

        # Look for structured communication match
        if self.name:
            overlook_partner = not self.partner_id  # If the transaction has no partner, look for match in payable and receivable account anyway
            domain = equal_amount_domain + [('ref', '=', self.name)]
            match_recs = self.get_move_lines_for_reconciliation(limit=2, additional_domain=domain, overlook_partner=overlook_partner)
            if match_recs and len(match_recs) != 1:
                return False

        # Look for a single move line with the same partner, the same amount
        if not match_recs:
            if self.partner_id:
                match_recs = self.get_move_lines_for_reconciliation(limit=2, additional_domain=equal_amount_domain)
                if match_recs and len(match_recs) != 1:
                    return False

        if not match_recs:
            return False

        # Now reconcile
        counterpart_aml_dicts = []
        payment_aml_rec = self.env['account.move.line']
        for aml in match_recs:
            if aml.account_id.internal_type == 'liquidity':
                payment_aml_rec = (payment_aml_rec | aml)
            else:
                amount = aml.currency_id and aml.amount_residual_currency or aml.amount_residual
                counterpart_aml_dicts.append({
                    'name': aml.name if aml.name != '/' else aml.move_id.name,
                    'debit': amount < 0 and -amount or 0,
                    'credit': amount > 0 and amount or 0,
                    'move_line': aml
                })

        try:
            with self._cr.savepoint():
                counterpart = self.process_reconciliation(counterpart_aml_dicts=counterpart_aml_dicts, payment_aml_rec=payment_aml_rec)
            return counterpart
        except UserError:
            # A configuration / business logic error that makes it impossible to auto-reconcile should not be raised
            # since automatic reconciliation is just an amenity and the user will get the same exception when manually
            # reconciling. Other types of exception are (hopefully) programmation errors and should cause a stacktrace.
            self.invalidate_cache()
            self.env['account.move'].invalidate_cache()
            self.env['account.move.line'].invalidate_cache()
            return False

    def _prepare_reconciliation_move(self, move_name):
        """ Prepare the dict of values to create the move from a statement line. This method may be overridden to adapt domain logic
            through model inheritance (make sure to call super() to establish a clean extension chain).

           :param char st_line_number: will be used as the name of the generated account move
           :return: dict of value to create() the account.move
        """
        return {
            'statement_line_id': self.id,
            'journal_id': self.statement_id.journal_id.id,
            'date': self.date,
            'name': move_name,
            'ref': self.ref,
        }

    def _prepare_reconciliation_move_line(self, move, amount):
        """ Prepare the dict of values to create the move line from a statement line.

            :param recordset move: the account.move to link the move line
            :param float amount: the amount of transaction that wasn't already reconciled
        """
        company_currency = self.journal_id.company_id.currency_id
        statement_currency = self.journal_id.currency_id or company_currency
        st_line_currency = self.currency_id or statement_currency

        amount_currency = False
        if statement_currency != company_currency or st_line_currency != company_currency:
            # First get the ratio total mount / amount not already reconciled
            if statement_currency == company_currency:
                total_amount = self.amount
            elif st_line_currency == company_currency:
                total_amount = self.amount_currency
            else:
                total_amount = statement_currency.with_context({'date': self.date}).compute(self.amount, company_currency)
            ratio = total_amount / amount
            # Then use it to adjust the statement.line field that correspond to the move.line amount_currency
            if statement_currency != company_currency:
                amount_currency = self.amount * ratio
            elif st_line_currency != company_currency:
                amount_currency = self.amount_currency * ratio
        return {
            'name': self.name,
            'date': self.date,
            'ref': self.ref,
            'move_id': move.id,
            'partner_id': self.partner_id and self.partner_id.id or False,
            'account_id': amount >= 0 \
                and self.statement_id.journal_id.default_credit_account_id.id \
                or self.statement_id.journal_id.default_debit_account_id.id,
            'credit': amount < 0 and -amount or 0.0,
            'debit': amount > 0 and amount or 0.0,
            'statement_id': self.statement_id.id,
            'journal_id': self.statement_id.journal_id.id,
            'currency_id': statement_currency != company_currency and statement_currency.id or (st_line_currency != company_currency and st_line_currency.id or False),
            'amount_currency': amount_currency,
        }

    @api.v7
    def process_reconciliations(self, cr, uid, ids, data, context=None):
        """ Handles data sent from the bank statement reconciliation widget (and can otherwise serve as an old-API bridge)

            :param list of dicts data: must contains the keys 'counterpart_aml_dicts', 'payment_aml_ids' and 'new_aml_dicts',
                whose value is the same as described in process_reconciliation except that ids are used instead of recordsets.
        """
        aml_obj = self.pool['account.move.line']
        for id, datum in zip(ids, data):
            st_line = self.browse(cr, uid, id, context)
            payment_aml_rec = aml_obj.browse(cr, uid, datum.get('payment_aml_ids', []), context)
            for aml_dict in datum.get('counterpart_aml_dicts', []):
                aml_dict['move_line'] = aml_obj.browse(cr, uid, aml_dict['counterpart_aml_id'], context)
                del aml_dict['counterpart_aml_id']
            st_line.process_reconciliation(datum.get('counterpart_aml_dicts', []), payment_aml_rec, datum.get('new_aml_dicts', []))

    def fast_counterpart_creation(self):
        for st_line in self:
            # Technical functionality to automatically reconcile by creating a new move line
            vals = {
                'name': st_line.name,
                'debit': st_line.amount < 0 and -st_line.amount or 0.0,
                'credit': st_line.amount > 0 and st_line.amount or 0.0,
                'account_id': st_line.account_id.id,
            }
            st_line.process_reconciliation(new_aml_dicts=[vals])

    def process_reconciliation(self, counterpart_aml_dicts=None, payment_aml_rec=None, new_aml_dicts=None):
        """ Match statement lines with existing payments (eg. checks) and/or payables/receivables (eg. invoices and refunds) and/or new move lines (eg. write-offs).
            If any new journal item needs to be created (via new_aml_dicts or counterpart_aml_dicts), a new journal entry will be created and will contain those
            items, as well as a journal item for the bank statement line.
            Finally, mark the statement line as reconciled by putting the matched moves ids in the column journal_entry_ids.

            :param (list of dicts) counterpart_aml_dicts: move lines to create to reconcile with existing payables/receivables.
                The expected keys are :
                - 'name'
                - 'debit'
                - 'credit'
                - 'move_line'
                    # The move line to reconcile (partially if specified debit/credit is lower than move line's credit/debit)

            :param (list of recordsets) payment_aml_rec: recordset move lines representing existing payments (which are already fully reconciled)

            :param (list of dicts) new_aml_dicts: move lines to create. The expected keys are :
                - 'name'
                - 'debit'
                - 'credit'
                - 'account_id'
                - (optional) 'tax_ids'
                - (optional) Other account.move.line fields like analytic_account_id or analytics_id

            :returns: The journal entries with which the transaction was matched. If there was at least an entry in counterpart_aml_dicts or new_aml_dicts, this list contains
                the move created by the reconciliation, containing entries for the statement.line (1), the counterpart move lines (0..*) and the new move lines (0..*).
        """
        counterpart_aml_dicts = counterpart_aml_dicts or []
        payment_aml_rec = payment_aml_rec or self.env['account.move.line']
        new_aml_dicts = new_aml_dicts or []

        aml_obj = self.env['account.move.line']

        company_currency = self.journal_id.company_id.currency_id
        statement_currency = self.journal_id.currency_id or company_currency
        st_line_currency = self.currency_id or statement_currency

        counterpart_moves = self.env['account.move']

        # Check and prepare received data
        if self.journal_entry_ids.ids:
            raise UserError(_('The bank statement line was already reconciled.'))
        if any(rec.statement_id for rec in payment_aml_rec):
            raise UserError(_('A selected move line was already reconciled.'))
        for aml_dict in counterpart_aml_dicts:
            if aml_dict['move_line'].reconciled:
                raise UserError(_('A selected move line was already reconciled.'))
            if isinstance(aml_dict['move_line'], (int, long)):
                aml_dict['move_line'] = aml_obj.browse(aml_dict['move_line'])
        for aml_dict in (counterpart_aml_dicts + new_aml_dicts):
            if aml_dict.get('tax_ids') and aml_dict['tax_ids'] and isinstance(aml_dict['tax_ids'][0], (int, long)):
                # Transform the value in the format required for One2many and Many2many fields
                aml_dict['tax_ids'] = map(lambda id: (4, id, None), aml_dict['tax_ids'])

        # Fully reconciled moves are just linked to the bank statement
        total = self.amount
        for aml_rec in payment_aml_rec:
            total -= aml_rec.debit-aml_rec.credit
            aml_rec.write({'statement_id': self.statement_id.id})
            aml_rec.move_id.write({'statement_line_id': self.id})
            counterpart_moves = (counterpart_moves | aml_rec.move_id)

        # Create move line(s). Either matching an existing journal entry (eg. invoice), in which
        # case we reconcile the existing and the new move lines together, or being a write-off.
        if counterpart_aml_dicts or new_aml_dicts:
            st_line_currency = self.currency_id or statement_currency
            st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False

            # Create the move
            move_name = (self.statement_id.name or self.name) + "/" + str(self.sequence)
            move_vals = self._prepare_reconciliation_move(move_name)
            move = self.env['account.move'].create(move_vals)
            counterpart_moves = (counterpart_moves | move)

            # Create The payment
            payment_id = False
            if abs(total)>0.00001:
                partner_id = self.partner_id and self.partner_id.id or False
                partner_type = False
                if partner_id:
                    if total < 0:
                        partner_type = 'supplier'
                    else:
                        partner_type = 'customer'

                payment_methods = (total>0) and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids
                currency = self.journal_id.currency_id or self.company_id.currency_id
                payment_id = self.env['account.payment'].create({
                    'payment_method_id': payment_methods and payment_methods[0].id or False,
                    'payment_type': total >0 and 'inbound' or 'outbound',
                    'partner_id': self.partner_id and self.partner_id.id or False,
                    'partner_type': partner_type,
                    'journal_id': self.statement_id.journal_id.id,
                    'payment_date': self.date,
                    'state': 'reconciled',
                    'currency_id': currency.id,
                    'amount': abs(total),
                    'communication': self.name or '',
                    'name': self.statement_id.name,
                }).id

            # Complete dicts to create both counterpart move lines and write-offs
            to_create = (counterpart_aml_dicts + new_aml_dicts)
            ctx = dict(self._context, date=self.date)
            for aml_dict in to_create:
                aml_dict['move_id'] = move.id
                aml_dict['partner_id'] = self.partner_id.id
                aml_dict['statement_id'] = self.statement_id.id
                if st_line_currency.id != company_currency.id:
                    aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
                    aml_dict['currency_id'] = st_line_currency.id
                    if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
                        # Statement is in company currency but the transaction is in foreign currency
                        aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
                        aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
                    elif self.currency_id and st_line_currency_rate:
                        # Statement is in foreign currency and the transaction is in another one
                        aml_dict['debit'] = statement_currency.with_context(ctx).compute(aml_dict['debit'] / st_line_currency_rate, company_currency)
                        aml_dict['credit'] = statement_currency.with_context(ctx).compute(aml_dict['credit'] / st_line_currency_rate, company_currency)
                    else:
                        # Statement is in foreign currency and no extra currency is given for the transaction
                        aml_dict['debit'] = st_line_currency.with_context(ctx).compute(aml_dict['debit'], company_currency)
                        aml_dict['credit'] = st_line_currency.with_context(ctx).compute(aml_dict['credit'], company_currency)
                elif statement_currency.id != company_currency.id:
                    # Statement is in foreign currency but the transaction is in company currency
                    prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
                    aml_dict['amount_currency'] = prorata_factor * self.amount
                    aml_dict['currency_id'] = statement_currency.id

            # Create the move line for the statement line using the total credit/debit of the counterpart
            # This leaves out the amount already reconciled and avoids rounding errors from currency conversion
            st_line_amount = sum(aml_dict['credit'] - aml_dict['debit'] for aml_dict in to_create)
            aml_dict = self._prepare_reconciliation_move_line(move, st_line_amount)
            aml_dict['payment_id'] = payment_id
            aml_obj.with_context(check_move_validity=False).create(aml_dict)

            # Create write-offs
            for aml_dict in new_aml_dicts:
                aml_dict['payment_id'] = payment_id
                aml_obj.with_context(check_move_validity=False).create(aml_dict)

            # Create counterpart move lines and reconcile them
            for aml_dict in counterpart_aml_dicts:
                if aml_dict['move_line'].partner_id.id:
                    aml_dict['partner_id'] = aml_dict['move_line'].partner_id.id
                aml_dict['account_id'] = aml_dict['move_line'].account_id.id
                aml_dict['payment_id'] = payment_id

                counterpart_move_line = aml_dict.pop('move_line')
                if counterpart_move_line.currency_id and counterpart_move_line.currency_id != company_currency and not aml_dict.get('currency_id'):
                    aml_dict['currency_id'] = counterpart_move_line.currency_id.id
                    aml_dict['amount_currency'] = company_currency.with_context(ctx).compute(aml_dict['debit'] - aml_dict['credit'], counterpart_move_line.currency_id)
                new_aml = aml_obj.with_context(check_move_validity=False).create(aml_dict)
                (new_aml | counterpart_move_line).reconcile()

            move.post()
        counterpart_moves.assert_balanced()
        return counterpart_moves
Beispiel #29
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')]
        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")
    trust = fields.Selection([('good', 'Good Debtor'),
                              ('normal', 'Normal Debtor'),
                              ('bad', 'Bad Debtor')],
                             string='Degree of trust you have in this debtor',
                             default='normal',
                             company_dependent=True)

    @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']
Beispiel #30
0
class product_supplier_info(models.Model):
    _inherit = 'product.supplierinfo'

    @api.depends('attribute_value_ids')
    def _get_price_extra_percentage(self):
        product_id = self.env.context and self.env.context.get(
            'product_id') or False
        for supplier in self:
            price_extra = 0.0
            for variant_id in supplier.attribute_value_ids:
                if product_id and variant_id.value not in product_id.attribute_value_ids:
                    continue
                if variant_id.price_extra_type != 'percentage' or supplier.id != variant_id.supplierinfo_id.id:
                    continue
                price_extra += variant_id.price_extra
            supplier.price_extra_perc = price_extra

    @api.depends('attribute_value_ids')
    def _get_price_extra(self):
        product_id = self.env.context and self.env.context.get(
            'product_id') or False
        for supplier in self:
            price_extra = 0.0
            for variant_id in supplier.attribute_value_ids:
                if product_id and variant_id.value not in product_id.attribute_value_ids:
                    continue
                if variant_id.price_extra_type != 'standard' or supplier.id != variant_id.supplierinfo_id.id:
                    continue
                price_extra += variant_id.price_extra
            supplier.price_extra = price_extra

    price_area_min_width = fields.Float(
        string="Min. Width",
        default=0.0,
        digits=dp.get_precision('Product Price'))
    price_area_max_width = fields.Float(
        string="Max. Width",
        default=0.0,
        digits=dp.get_precision('Product Price'))
    price_area_min_height = fields.Float(
        string="Min. Height",
        default=0.0,
        digits=dp.get_precision('Product Price'))
    price_area_max_height = fields.Float(
        string="Max. Height",
        default=0.0,
        digits=dp.get_precision('Product Price'))
    min_price_area = fields.Monetary('Min. Price')

    price_type = fields.Selection(
        PRICE_TYPES,
        string='Supplier Price Type',
        required=True,
        default='standard',
    )
    prices_table = fields.One2many('product.prices_table',
                                   'supplier_product_id',
                                   string="Supplier Prices Table")

    attribute_value_ids = fields.One2many(
        comodel_name='supplier.attribute.value',
        inverse_name='supplierinfo_id')

    price_extra = fields.Float(
        compute='_get_price_extra',
        string='Variant Extra Price',
        help="This is the sum of the extra price of all attributes",
        digits_compute=dp.get_precision('Product Price'))
    price_extra_perc = fields.Float(
        compute='_get_price_extra_percentage',
        string='Variant Extra Price Percentage',
        help="This is the percentage of the extra price of all attributes",
        digits_compute=dp.get_precision('Product Price'))

    def get_price_table_headers(self):
        result = {'x': [0], 'y': [0]}
        for rec in self.prices_table:
            result['x'].append(rec.pos_x)
            result['y'].append(rec.pos_y)
        result.update({
            'x': sorted(list(set(result['x']))),
            'y': sorted(list(set(result['y'])))
        })
        return result

    # TODO: Estos metodos necesitarán ser reescritos cuando se usen los atributos
    def manzano_check_dim_values(self, width, height):
        if self.price_type in ['table_1d', 'table_2d']:
            product_prices_table_obj = self.env['product.prices_table']
            norm_width = self.manzano_normalize_width_value(width)
            if self.price_type == 'table_2d':
                norm_height = self.manzano_normalize_height_value(height)
                return product_prices_table_obj.search_count(
                    [('supplier_product_id', '=', self.id),
                     ('pos_x', '=', norm_width), ('pos_y', '=', norm_height),
                     ('value', '!=', 0)]) > 0
            return product_prices_table_obj.search_count(
                [('supplier_product_id', '=', self.id),
                 ('pos_x', '=', norm_width), ('value', '!=', 0)]) > 0
        elif self.price_type == 'area':
            return width >= self.price_area_min_width and width <= self.price_area_max_width and height >= self.price_area_min_height and height <= self.price_area_max_height
        return True

    def manzano_normalize_width_value(self, width):
        headers = self.get_price_table_headers()
        norm_val = width
        for index in range(len(headers['x']) - 1):
            if width > headers['x'][index] and width <= headers['x'][index +
                                                                     1]:
                norm_val = headers['x'][index + 1]
        return norm_val

    def manzano_normalize_height_value(self, height):
        headers = self.get_price_table_headers()
        norm_val = height
        for index in range(len(headers['y']) - 1):
            if height > headers['y'][index] and height <= headers['y'][index +
                                                                       1]:
                norm_val = headers['y'][index + 1]
        return norm_val

    @api.depends('price')
    def get_supplier_price(self):
        # FIXME: Mejor usar atributos
        manzano_width = self.env.context and self.env.context.get(
            'width') or False
        manzano_height = self.env.context and self.env.context.get(
            'height') or False
        product_id = self.env.context and self.env.context.get(
            'product_id') or False

        result = {}
        for record in self:
            result[record.id] = False
            if manzano_width:
                product_prices_table_obj = self.env['product.prices_table']
                manzano_width = record.manzano_normalize_width_value(
                    manzano_width)
                if record.price_type == 'table_2d':
                    manzano_height = record.manzano_normalize_height_value(
                        manzano_height)
                    res = product_prices_table_obj.search(
                        [('supplier_product_id', '=', record.id),
                         ('pos_x', '=', manzano_width),
                         ('pos_y', '=', manzano_height)],
                        limit=1)
                    result[record.id] = res and res.value or False
                elif record.price_type == 'table_1d':
                    res = product_prices_table_obj.search(
                        [('supplier_product_id', '=', record.id),
                         ('pos_x', '=', manzano_width)],
                        limit=1)
                    result[record.id] = res and res.value or False
                elif record.price_type == 'area':
                    result[record.
                           id] = record.price * manzano_width * manzano_height
                    result[record.id] = max(record.min_price_area,
                                            result[record.id])
            if not result[record.id]:
                result[record.id] = record.price
            result[record.id] += record.with_context(
                product_id=product_id).price_extra + record.with_context(
                    product_id=product_id).price_extra_perc
        return result

    # ---

    # -- START Original source by 'Prev. Manzano Dev.'
    @api.multi
    def action_open_value_extras(self):
        self.ensure_one()
        extra_ds = self.env['supplier.attribute.value']
        for line in self.product_tmpl_id.attribute_line_ids:
            for value in line.value_ids:
                extra = extra_ds.search([('supplierinfo_id', '=', self.id),
                                         ('value', '=', value.id)])
                if not extra:
                    extra = extra_ds.create({
                        'supplierinfo_id': self.id,
                        'value': value.id,
                    })
                extra_ds |= extra
        all_supplierinfo_extra = extra_ds.search([('supplierinfo_id', '=',
                                                   self.id)])
        remove_extra = all_supplierinfo_extra - extra_ds
        remove_extra.unlink()
        result = self.product_tmpl_id._get_act_window_dict(
            'price_dimension.supplier_attribute_value_action')
        return result