class KsGlobalDiscountInvoice(models.Model):
    # _inherit = "account.invoice"
    """ changing the model to account.move """
    _inherit = "account.move"

    ks_global_discount_type = fields.Selection(
        [('percent', 'Percentage'), ('amount', 'Amount')],
        string='Universal Discount Type',
        readonly=True,
        states={
            'draft': [('readonly', False)],
            'sent': [('readonly', False)]
        },
        default='percent')
    ks_global_discount_rate = fields.Float('Universal Discount',
                                           readonly=True,
                                           states={
                                               'draft': [('readonly', False)],
                                               'sent': [('readonly', False)]
                                           })
    ks_amount_discount = fields.Monetary(string='Universal Discount',
                                         readonly=True,
                                         compute='_compute_amount',
                                         store=True,
                                         track_visibility='always')
    ks_enable_discount = fields.Boolean(compute='ks_verify_discount')
    ks_sales_discount_account = fields.Text(compute='ks_verify_discount')
    ks_purchase_discount_account = fields.Text(compute='ks_verify_discount')

    # @api.multi
    @api.depends('name')
    def ks_verify_discount(self):
        for rec in self:
            rec.ks_enable_discount = rec.env['ir.config_parameter'].sudo(
            ).get_param('ks_enable_discount')
            rec.ks_sales_discount_account = rec.env[
                'ir.config_parameter'].sudo().get_param(
                    'ks_sales_discount_account')
            rec.ks_purchase_discount_account = rec.env[
                'ir.config_parameter'].sudo().get_param(
                    'ks_purchase_discount_account')

    # @api.multi
    # 1. tax_line_ids is replaced with tax_line_id. 2. api.mulit is also removed.
    # @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'tax_line_ids.amount_rounding',
    #              'currency_id', 'company_id', 'date_invoice', 'type', 'ks_global_discount_type',
    #              'ks_global_discount_rate')
    @api.depends('line_ids.debit', 'line_ids.credit', 'line_ids.currency_id',
                 'line_ids.amount_currency', 'line_ids.amount_residual',
                 'line_ids.amount_residual_currency',
                 'line_ids.payment_id.state', 'ks_global_discount_type',
                 'ks_global_discount_rate')
    def _compute_amount(self):
        for rec in self:
            res = super(KsGlobalDiscountInvoice, rec)._compute_amount()
            if not ('ks_global_tax_rate' in rec):
                rec.ks_calculate_discount()
            sign = rec.type in ['in_refund', 'out_refund'] and -1 or 1
            rec.amount_total_company_signed = rec.amount_total * sign
            rec.amount_total_signed = rec.amount_total * sign
        return res

    # @api.multi
    def ks_calculate_discount(self):
        for rec in self:
            if rec.ks_global_discount_type == "amount":
                rec.ks_amount_discount = rec.ks_global_discount_rate if rec.amount_untaxed > 0 else 0
            elif rec.ks_global_discount_type == "percent":
                if rec.ks_global_discount_rate != 0.0:
                    rec.ks_amount_discount = (
                        rec.amount_untaxed +
                        rec.amount_tax) * rec.ks_global_discount_rate / 100
                else:
                    rec.ks_amount_discount = 0
            elif not rec.ks_global_discount_type:
                rec.ks_global_discount_rate = 0
                rec.ks_amount_discount = 0
            rec.amount_total = rec.amount_tax + rec.amount_untaxed - rec.ks_amount_discount
            rec.ks_update_universal_discount()

    @api.constrains('ks_global_discount_rate')
    def ks_check_discount_value(self):
        if self.ks_global_discount_type == "percent":
            if self.ks_global_discount_rate > 100 or self.ks_global_discount_rate < 0:
                raise ValidationError(
                    'You cannot enter percentage value greater than 100.')
        else:
            if self.ks_global_discount_rate < 0 or self.amount_untaxed < 0:
                raise ValidationError(
                    'You cannot enter discount amount greater than actual cost or value lower than 0.'
                )

    # @api.onchange('purchase_id')
    # def ks_get_purchase_order_discount(self):
    #     self.ks_global_discount_rate = self.purchase_id.ks_global_discount_rate
    #     self.ks_global_discount_type = self.purchase_id.ks_global_discount_type

    # @api.model
    # def invoice_line_move_line_get(self):
    #     ks_res = super(KsGlobalDiscountInvoice, self).invoice_line_move_line_get()
    #     if self.ks_amount_discount > 0:
    #         ks_name = "Universal Discount"
    #         if self.ks_global_discount_type == "percent":
    #             ks_name = ks_name + " (" + str(self.ks_global_discount_rate) + "%)"
    #         ks_name = ks_name + " for " + (self.origin if self.origin else ("Invoice No " + str(self.id)))
    #         if self.ks_sales_discount_account and (self.type == "out_invoice" or self.type == "out_refund"):
    #
    #             dict = {
    #                 'invl_id': self.number,
    #                 'type': 'src',
    #                 'name': ks_name,
    #                 'price_unit': self.move_id.ks_amount_discount,
    #                 'quantity': 1,
    #                 'amount': -self.move_id.ks_amount_discount,
    #                 'account_id': int(self.move_id.ks_sales_discount_account),
    #                 'move_id': self.id,
    #                 'date': self.date,
    #                 'user_id': self.move_id.invoice_user_id.id or self._uid,
    #                 'company_id': self.move_id.account_id.company_id.id or self.env.company.id,
    #             }
    #             ks_res.append(dict)
    #
    #         elif self.ks_purchase_discount_account and (self.type == "in_invoice" or self.type == "in_refund"):
    #             dict = {
    #                 'invl_id': self.number,
    #                 'type': 'src',
    #                 'name': ks_name,
    #                 'price_unit': self.ks_amount_discount,
    #                 'quantity': 1,
    #                 'price': -self.ks_amount_discount,
    #                 'account_id': int(self.ks_purchase_discount_account),
    #
    #                 'invoice_id': self.id,
    #             }
    #             ks_res.append(dict)
    #
    #     return ks_res

    @api.model
    def _prepare_refund(self,
                        invoice,
                        date_invoice=None,
                        date=None,
                        description=None,
                        journal_id=None):
        ks_res = super(KsGlobalDiscountInvoice,
                       self)._prepare_refund(invoice,
                                             date_invoice=None,
                                             date=None,
                                             description=None,
                                             journal_id=None)
        ks_res['ks_global_discount_rate'] = self.ks_global_discount_rate
        ks_res['ks_global_discount_type'] = self.ks_global_discount_type
        return ks_res

    def ks_update_universal_discount(self):
        """This Function Updates the Universal Discount through Sale Order"""
        for rec in self:
            already_exists = self.line_ids.filtered(
                lambda line: line.name and line.name.find('Universal Discount'
                                                          ) == 0)
            terms_lines = self.line_ids.filtered(
                lambda line: line.account_id.user_type_id.type in
                ('receivable', 'payable'))
            other_lines = self.line_ids.filtered(
                lambda line: line.account_id.user_type_id.type not in
                ('receivable', 'payable'))
            if already_exists:
                amount = rec.ks_amount_discount
                if rec.ks_sales_discount_account \
                        and (rec.type == "out_invoice"
                             or rec.type == "out_refund")\
                        and amount > 0:
                    if rec.type == "out_invoice":
                        already_exists.update({
                            'debit':
                            amount > 0.0 and amount or 0.0,
                            'credit':
                            amount < 0.0 and -amount or 0.0,
                        })
                    else:
                        already_exists.update({
                            'debit':
                            amount < 0.0 and -amount or 0.0,
                            'credit':
                            amount > 0.0 and amount or 0.0,
                        })
                if rec.ks_purchase_discount_account \
                        and (rec.type == "in_invoice"
                             or rec.type == "in_refund")\
                        and amount > 0:
                    if rec.type == "in_invoice":
                        already_exists.update({
                            'debit':
                            amount < 0.0 and -amount or 0.0,
                            'credit':
                            amount > 0.0 and amount or 0.0,
                        })
                    else:
                        already_exists.update({
                            'debit':
                            amount > 0.0 and amount or 0.0,
                            'credit':
                            amount < 0.0 and -amount or 0.0,
                        })
                total_balance = sum(other_lines.mapped('balance'))
                total_amount_currency = sum(
                    other_lines.mapped('amount_currency'))
                terms_lines.update({
                    'amount_currency':
                    -total_amount_currency,
                    'debit':
                    total_balance < 0.0 and -total_balance or 0.0,
                    'credit':
                    total_balance > 0.0 and total_balance or 0.0,
                })
            if not already_exists and rec.ks_global_discount_rate > 0:
                in_draft_mode = self != self._origin
                if not in_draft_mode and rec.type == 'out_invoice':
                    rec._recompute_universal_discount_lines()
                print()

    @api.onchange('ks_global_discount_rate', 'ks_global_discount_type',
                  'line_ids')
    def _recompute_universal_discount_lines(self):
        """This Function Create The General Entries for Universal Discount"""
        for rec in self:
            type_list = [
                'out_invoice', 'out_refund', 'in_invoice', 'in_refund'
            ]
            if rec.ks_global_discount_rate > 0 and rec.type in type_list:
                if rec.is_invoice(include_receipts=True):
                    in_draft_mode = self != self._origin
                    ks_name = "Universal Discount "
                    if rec.ks_global_discount_type == "amount":
                        ks_value = "of amount #" + str(
                            self.ks_global_discount_rate)
                    elif rec.ks_global_discount_type == "percent":
                        ks_value = " @" + str(
                            self.ks_global_discount_rate) + "%"
                    else:
                        ks_value = ''
                    ks_name = ks_name + ks_value
                    #           ("Invoice No: " + str(self.ids)
                    #            if self._origin.id
                    #            else (self.display_name))
                    terms_lines = self.line_ids.filtered(
                        lambda line: line.account_id.user_type_id.type in
                        ('receivable', 'payable'))
                    already_exists = self.line_ids.filtered(
                        lambda line: line.name and line.name.find(
                            'Universal Discount') == 0)
                    if already_exists:
                        amount = self.ks_amount_discount
                        if self.ks_sales_discount_account \
                                and (self.type == "out_invoice"
                                     or self.type == "out_refund"):
                            if self.type == "out_invoice":
                                already_exists.update({
                                    'name':
                                    ks_name,
                                    'debit':
                                    amount > 0.0 and amount or 0.0,
                                    'credit':
                                    amount < 0.0 and -amount or 0.0,
                                })
                            else:
                                already_exists.update({
                                    'name':
                                    ks_name,
                                    'debit':
                                    amount < 0.0 and -amount or 0.0,
                                    'credit':
                                    amount > 0.0 and amount or 0.0,
                                })
                        if self.ks_purchase_discount_account\
                                and (self.type == "in_invoice"
                                     or self.type == "in_refund"):
                            if self.type == "in_invoice":
                                already_exists.update({
                                    'name':
                                    ks_name,
                                    'debit':
                                    amount < 0.0 and -amount or 0.0,
                                    'credit':
                                    amount > 0.0 and amount or 0.0,
                                })
                            else:
                                already_exists.update({
                                    'name':
                                    ks_name,
                                    'debit':
                                    amount > 0.0 and amount or 0.0,
                                    'credit':
                                    amount < 0.0 and -amount or 0.0,
                                })
                    else:
                        new_tax_line = self.env['account.move.line']
                        create_method = in_draft_mode and \
                                        self.env['account.move.line'].new or\
                                        self.env['account.move.line'].create

                        if self.ks_sales_discount_account \
                                and (self.type == "out_invoice"
                                     or self.type == "out_refund"):
                            amount = self.ks_amount_discount
                            dict = {
                                'move_name':
                                self.name,
                                'name':
                                ks_name,
                                'price_unit':
                                self.ks_amount_discount,
                                'quantity':
                                1,
                                'debit':
                                amount < 0.0 and -amount or 0.0,
                                'credit':
                                amount > 0.0 and amount or 0.0,
                                'account_id':
                                int(self.ks_sales_discount_account),
                                'move_id':
                                self._origin,
                                'date':
                                self.date,
                                'exclude_from_invoice_tab':
                                True,
                                'partner_id':
                                terms_lines.partner_id.id,
                                'company_id':
                                terms_lines.company_id.id,
                                'company_currency_id':
                                terms_lines.company_currency_id.id,
                            }
                            if self.type == "out_invoice":
                                dict.update({
                                    'debit':
                                    amount > 0.0 and amount or 0.0,
                                    'credit':
                                    amount < 0.0 and -amount or 0.0,
                                })
                            else:
                                dict.update({
                                    'debit':
                                    amount < 0.0 and -amount or 0.0,
                                    'credit':
                                    amount > 0.0 and amount or 0.0,
                                })
                            if in_draft_mode:
                                self.line_ids += create_method(dict)
                                # Updation of Invoice Line Id
                                duplicate_id = self.invoice_line_ids.filtered(
                                    lambda line: line.name and line.name.find(
                                        'Universal Discount') == 0)
                                self.invoice_line_ids = self.invoice_line_ids - duplicate_id
                            else:
                                dict.update({
                                    'price_unit': 0.0,
                                    'debit': 0.0,
                                    'credit': 0.0,
                                })
                                self.line_ids = [(0, 0, dict)]

                        if self.ks_purchase_discount_account\
                                and (self.type == "in_invoice"
                                     or self.type == "in_refund"):
                            amount = self.ks_amount_discount
                            dict = {
                                'move_name':
                                self.name,
                                'name':
                                ks_name,
                                'price_unit':
                                self.ks_amount_discount,
                                'quantity':
                                1,
                                'debit':
                                amount > 0.0 and amount or 0.0,
                                'credit':
                                amount < 0.0 and -amount or 0.0,
                                'account_id':
                                int(self.ks_purchase_discount_account),
                                'move_id':
                                self.id,
                                'date':
                                self.date,
                                'exclude_from_invoice_tab':
                                True,
                                'partner_id':
                                terms_lines.partner_id.id,
                                'company_id':
                                terms_lines.company_id.id,
                                'company_currency_id':
                                terms_lines.company_currency_id.id,
                            }

                            if self.type == "in_invoice":
                                dict.update({
                                    'debit':
                                    amount < 0.0 and -amount or 0.0,
                                    'credit':
                                    amount > 0.0 and amount or 0.0,
                                })
                            else:
                                dict.update({
                                    'debit':
                                    amount > 0.0 and amount or 0.0,
                                    'credit':
                                    amount < 0.0 and -amount or 0.0,
                                })
                            self.line_ids += create_method(dict)
                            # updation of invoice line id
                            duplicate_id = self.invoice_line_ids.filtered(
                                lambda line: line.name and line.name.find(
                                    'Universal Discount') == 0)
                            self.invoice_line_ids = self.invoice_line_ids - duplicate_id

                    if in_draft_mode:
                        # Update the payement account amount
                        terms_lines = self.line_ids.filtered(
                            lambda line: line.account_id.user_type_id.type in
                            ('receivable', 'payable'))
                        other_lines = self.line_ids.filtered(
                            lambda line: line.account_id.user_type_id.type
                            not in ('receivable', 'payable'))
                        total_balance = sum(other_lines.mapped('balance'))
                        total_amount_currency = sum(
                            other_lines.mapped('amount_currency'))
                        terms_lines.update({
                            'amount_currency':
                            -total_amount_currency,
                            'debit':
                            total_balance < 0.0 and -total_balance or 0.0,
                            'credit':
                            total_balance > 0.0 and total_balance or 0.0,
                        })
                    else:
                        terms_lines = self.line_ids.filtered(
                            lambda line: line.account_id.user_type_id.type in
                            ('receivable', 'payable'))
                        other_lines = self.line_ids.filtered(
                            lambda line: line.account_id.user_type_id.type
                            not in ('receivable', 'payable'))
                        already_exists = self.line_ids.filtered(
                            lambda line: line.name and line.name.find(
                                'Universal Discount') == 0)
                        total_balance = sum(
                            other_lines.mapped('balance')) + amount
                        total_amount_currency = sum(
                            other_lines.mapped('amount_currency'))
                        dict1 = {
                            'debit': amount > 0.0 and amount or 0.0,
                            'credit': amount < 0.0 and -amount or 0.0,
                        }
                        dict2 = {
                            'debit': total_balance < 0.0 and -total_balance
                            or 0.0,
                            'credit': total_balance > 0.0 and total_balance
                            or 0.0,
                        }
                        self.line_ids = [(1, already_exists.id, dict1),
                                         (1, terms_lines.id, dict2)]
                        print()

            elif self.ks_global_discount_rate <= 0:
                already_exists = self.line_ids.filtered(
                    lambda line: line.name and line.name.find(
                        'Universal Discount') == 0)
                if already_exists:
                    self.line_ids -= already_exists
                    terms_lines = self.line_ids.filtered(
                        lambda line: line.account_id.user_type_id.type in
                        ('receivable', 'payable'))
                    other_lines = self.line_ids.filtered(
                        lambda line: line.account_id.user_type_id.type not in
                        ('receivable', 'payable'))
                    total_balance = sum(other_lines.mapped('balance'))
                    total_amount_currency = sum(
                        other_lines.mapped('amount_currency'))
                    terms_lines.update({
                        'amount_currency':
                        -total_amount_currency,
                        'debit':
                        total_balance < 0.0 and -total_balance or 0.0,
                        'credit':
                        total_balance > 0.0 and total_balance or 0.0,
                    })
class PaymentAccountMoveLine(models.TransientModel):
    _name = "payment.account.move.line"
    _description = "Assistente Para Lançamento de Pagamentos"

    company_id = fields.Many2one(
        "res.company",
        related="journal_id.company_id",
        string="Exmpresa",
        readonly=True,
    )
    move_line_id = fields.Many2one(
        "account.move.line", readonly=True, string="Conta à Pagar/Receber"
    )
    move_id = fields.Many2one("account.move", readonly=True, string="Fatura")
    partner_type = fields.Selection(
        [("customer", "Cliente"), ("supplier", "Fornecedor")], readonly=True
    )
    partner_id = fields.Many2one(
        "res.partner", string="Cliente/Fornecedor", readonly=True
    )
    journal_id = fields.Many2one(
        "account.journal",
        string="Diário",
        required=True,
        domain=[("type", "in", ("bank", "cash"))],
    )
    communication = fields.Char(string="Anotações")
    payment_date = fields.Date(
        string="Data do Pagamento",
        default=fields.Date.context_today,
        required=True,
    )
    currency_id = fields.Many2one(
        "res.currency",
        string="Moeda",
        required=True,
        default=lambda self: self.env.user.company_id.currency_id,
    )
    amount_residual = fields.Monetary(
        string="Saldo", readonly=True, related="move_line_id.amount_residual"
    )
    amount = fields.Monetary(string="Valor do Pagamento", required=True,)

    @api.model
    def default_get(self, fields):
        rec = super(PaymentAccountMoveLine, self).default_get(fields)
        move_line_id = rec.get("move_line_id", False)
        amount = 0
        if not move_line_id:
            raise UserError(
                _("Não foi selecionada nenhuma linha de cobrança.")
            )
        move_line = self.env["account.move.line"].browse(move_line_id)
        if move_line[0].amount_residual:
            amount = (
                move_line[0].amount_residual
                if rec["partner_type"] == "customer"
                else move_line[0].amount_residual * -1
            )
        if move_line[0].move_id:
            rec.update({"move_id": move_line[0].move_id.id})
        rec.update(
            {"amount": amount,}
        )

        return rec

    def _get_payment_vals(self):
        """
        Method responsible for generating payment record amounts
        """
        payment_type = "inbound" if self.move_line_id.debit else "outbound"
        payment_methods = (
            payment_type == "inbound"
            and self.journal_id.inbound_payment_method_ids
            or self.journal_id.outbound_payment_method_ids
        )
        payment_method_id = payment_methods and payment_methods[0].id or False
        if not payment_method_id:
            domain = [("payment_type", "=", payment_type)]
            payment_method_id = (
                self.env["account.payment.method"].search(domain, limit=1).id
            )
        vals = {
            "partner_id": self.partner_id.id,
            "move_line_ids": [(4, 0, self.move_line_id.id)],
            "journal_id": self.journal_id.id,
            "communication": self.communication,
            "amount": self.amount,
            "payment_date": self.payment_date,
            "payment_type": payment_type,
            "payment_method_id": payment_method_id,
            "currency_id": self.currency_id.id,
        }
        return vals

    def action_confirm_payment(self):
        """
        Method responsible for creating the payment
        """
        payment = self.env["account.payment"]
        vals = self._get_payment_vals()
        pay = payment.with_context(
            force_counterpart_account=self.move_line_id.account_id.id
        ).create(vals)
        pay.post()
        move_line = self.env["account.move.line"].browse(
            vals["move_line_ids"][0][2]
        )
        lines_to_reconcile = (pay.move_line_ids + move_line).filtered(
            lambda l: l.account_id == move_line.account_id
        )
        lines_to_reconcile.reconcile()
Exemple #3
0
class ProcessMailsDocument(models.Model):
    _name = 'mail.message.dte.document'
    _description = "Pre Documento Recibido"
    _inherit = ['mail.thread']

    dte_id = fields.Many2one(
        'mail.message.dte',
        string="DTE",
        readonly=True,
        ondelete='cascade',
    )
    new_partner = fields.Char(
        string="Proveedor Nuevo",
        readonly=True,
    )
    partner_id = fields.Many2one(
        'res.partner',
        string='Proveedor',
        domain=[('supplier', '=', True)],
    )
    date = fields.Date(
        string="Fecha Emsisión",
        readonly=True,
    )
    number = fields.Char(
        string='Folio',
        readonly=True,
    )
    document_class_id = fields.Many2one(
        'sii.document_class',
        string="Tipo de Documento",
        readonly=True,
        oldname="sii_document_class_id",
    )
    amount = fields.Monetary(
        string="Monto",
        readonly=True,
    )
    currency_id = fields.Many2one(
        'res.currency',
        string="Moneda",
        readonly=True,
        default=lambda self: self.env.user.company_id.currency_id,
    )
    invoice_line_ids = fields.One2many(
        'mail.message.dte.document.line',
        'document_id',
        string="Líneas del Documento",
    )
    company_id = fields.Many2one(
        'res.company',
        string="Compañía",
        readonly=True,
    )
    state = fields.Selection(
        [
            ('draft', 'Recibido'),
            ('accepted', 'Aceptado'),
            ('rejected', 'Rechazado'),
        ],
        default='draft',
    )
    invoice_id = fields.Many2one(
        'account.invoice',
        string="Factura",
        readonly=True,
    )
    xml = fields.Text(
        string="XML Documento",
        readonly=True,
    )
    purchase_to_done = fields.Many2many(
        'purchase.order',
        string="Ordenes de Compra a validar",
        domain=[('state', 'not in', ['accepted', 'rejected'])],
    )
    claim = fields.Selection(
        [
            ('N/D', "No definido"),
            ('ACD', 'Acepta Contenido del Documento'),
            ('RCD', 'Reclamo al  Contenido del Documento '),
            ('ERM', ' Otorga  Recibo  de  Mercaderías  o Servicios'),
            ('RFP', 'Reclamo por Falta Parcial de Mercaderías'),
            ('RFT', 'Reclamo por Falta Total de Mercaderías'),
        ],
        string="Reclamo",
        copy=False,
        default="N/D",
    )
    claim_description = fields.Char(
        string="Detalle Reclamo",
        readonly=True,
    )

    _order = 'create_date DESC'

    @api.model
    def auto_accept_documents(self):
        self.env.cr.execute("""
            select
                id
            from
                mail_message_dte_document
            where
                create_date + interval '8 days' < now()
                and
                state = 'draft'
            """)
        for d in self.browse([line.get('id') for line in \
                              self.env.cr.dictfetchall()]):
            d.accept_document()

    @api.multi
    def accept_document(self):
        created = []
        for r in self:
            vals = {
                'xml_file': r.xml.encode('ISO-8859-1'),
                'filename': r.dte_id.name,
                'pre_process': False,
                'document_id': r.id,
                'option': 'accept'
            }
            val = self.env['sii.dte.upload_xml.wizard'].create(vals)
            resp = val.confirm(ret=True)
            created.extend(resp)
            r.get_dte_claim()
            for i in self.env['account.invoice'].browse(resp):
                if i.claim in ['ACD', 'ERM']:
                    r.state = 'accepted'
        xml_id = 'account.action_invoice_tree2'
        result = self.env.ref('%s' % (xml_id)).read()[0]
        if created:
            domain = safe_eval(result.get('domain', '[]'))
            domain.append(('id', 'in', created))
            result['domain'] = domain
        return result

    @api.multi
    def reject_document(self):
        for r in self:
            r.set_dte_claim(claim='RCD')
            if r.claim in ['RCD']:
                r.state = 'rejected'

    def set_dte_claim(self, claim):
        if self.document_class_id.sii_code not in [33, 34, 43]:
            self.claim = claim
            return
        if not self.partner_id:
            rut_emisor = self.new_partner.split(' ')[0]
        else:
            rut_emisor = self.env['account.invoice'].format_vat(
                self.partner_id.vat)
        token = self.env['sii.xml.envio'].get_token(self.env.user,
                                                    self.company_id)
        url = claim_url[self.company_id.dte_service_provider] + '?wsdl'
        _server = Client(
            url,
            headers={
                'Cookie': 'TOKEN=' + token,
            },
        )
        try:
            respuesta = _server.service.ingresarAceptacionReclamoDoc(
                rut_emisor[:-2],
                rut_emisor[-1],
                str(self.document_class_id.sii_code),
                str(self.number),
                claim,
            )
        except Exception as e:
            msg = "Error al ingresar Reclamo DTE"
            _logger.warning("%s: %s" % (msg, str(e)))
            if e.args[0][0] == 503:
                raise UserError(
                    '%s: Conexión al SII caída/rechazada o el SII está temporalmente fuera de línea, reintente la acción'
                    % (msg))
            raise UserError(("%s: %s" % (msg, str(e))))
        self.claim_description = respuesta
        if respuesta.codResp in [0, 7]:
            self.claim = claim

    @api.multi
    def get_dte_claim(self):
        if not self.partner_id:
            rut_emisor = self.new_partner.split(' ')[0]
        else:
            rut_emisor = self.env['account.invoice'].format_vat(
                self.partner_id.vat)
        token = self.env['sii.xml.envio'].get_token(self.env.user,
                                                    self.company_id)
        url = claim_url[self.company_id.dte_service_provider] + '?wsdl'
        _server = Client(
            url,
            headers={
                'Cookie': 'TOKEN=' + token,
            },
        )
        try:
            respuesta = _server.service.listarEventosHistDoc(
                rut_emisor[:-2],
                rut_emisor[-1],
                str(self.document_class_id.sii_code),
                str(self.number),
            )
            self.claim_description = respuesta
        except Exception as e:
            _logger.warning("Error al obtener aceptación %s" % (str(e)))
            if self.company_id.dte_service_provider == 'SII':
                raise UserError("Error al obtener aceptación: %s" % str(e))
class Project(models.Model):

    _name = 'project.project'
    _inherit = ['project.project', 'mail.thread', 'mail.activity.mixin']
    _order = 'name'

    # We Override this method from 'project_task_default_stage
    def _get_default_type_common(self):
        ids = self.env['project.task.type'].search([
            ('case_default', '=', True),
            ('project_type_default', '=', self.project_type)])
        _logger.info("Default Stages: {} for project type {}".format(ids.mapped('name'),self.project_type))
        return ids

    type_ids = fields.Many2many(
        default=lambda self: self._get_default_type_common())

    user_id = fields.Many2one('res.users', string='Project Manager', default=lambda self: self._default_user_id() , track_visibility="onchange")

    project_type = fields.Selection([
        ('dev', 'Developement'),
        ('client', 'Client'),
        ('internal','Internal')],
        string='Project Type',
        default='client',
    )

    # Maximum parent level here is meant to be 1 at max for now
    parent_id = fields.Many2one(
        'project.project', 'Parent project',
        index=True, ondelete='cascade',
        compute='_get_parent_id',
        store=True
    )
    child_id = fields.One2many('project.project', 'parent_id', 'Child projects')

    parent_task_count = fields.Integer(
        compute='_compute_parent_task_count'
    )

    child_task_count = fields.Integer(
        compute='_compute_child_task_count'
    )

    #consultant_ids = fields.Many2many('hr.employee', string='Consultants')
    #ta_ids = fields.Many2many('hr.employee', string='Ta')
    completion_ratio = fields.Float('Task Complete', compute='compute_project_completion_ratio', store=True, help='All parent tasks completion %, weighted by contractual budget')
    consummed_completed_ratio = fields.Float('BC / TC',
                                             compute='compute_project_consummed_completed_ratio', store=True, help='BC/TC in Percentage, lower is "better", 100 is on target')
    summary_ids = fields.One2many(
        'project.summary', 'project_id',
        'Project summaries'
    )
    is_project_manager = fields.Boolean(compute="_get_is_project_manager")
    scope_of_work = fields.Html(string='Scope of work')
    orders_count = fields.Integer(compute='compute_orders_count')
    tasks_count = fields.Integer()
    timesheets_count = fields.Integer()
    lead_consultant = fields.Many2one('hr.employee', related='core_team_id.lead_consultant', string='Lead Consultant')
    assistant_id = fields.Many2one('hr.employee', related='core_team_id.assistant_id', string='Project Assistant')
    lead_backup = fields.Many2one('hr.employee', related='core_team_id.lead_backup',
                                  string='Lead Consultant Backup')
    consultant_ids = fields.Many2many('hr.employee', 'rel_project_consultants', related='core_team_id.consultant_ids',
                                      string='Consultants')
    ta_ids = fields.Many2many('hr.employee', relation='rel_project_tas', related='core_team_id.ta_ids',
                              string='Ta')
    controller_id = fields.Many2one('res.users', related='partner_id.controller_id', string='Project Controller')
    invoice_admin_id = fields.Many2one('res.users', related='partner_id.invoice_admin_id', string='Invoice Administrator')
    account_manager_id = fields.Many2one('res.users', related='sale_order_id.user_id', string='Account Manager', store=True)

    invoices_count = fields.Integer(
        compute='_get_out_invoice_ids',
        compute_sudo = True
    )
    out_invoice_ids = fields.Many2many(
        string='Out invoices',
        compute='_get_out_invoice_ids',
        comodel_name="account.invoice",
        relation="project_out_invoice_rel",
        column1="project_id",
        column2="invoice_id",
        store=True,
        copy=False, readonly=True,
        compute_sudo=True
    )

    risk_ids = fields.Many2many(
        'risk', string='Risk',
        compute='_compute_risk_ids',
        # store=True,
    )

    risk_score = fields.Integer(
        string='Risk Score',
        compute='_compute_risk_score',
        store=True,
    )

    risk_last_update = fields.Datetime(
        string='Risk Last Update',
        compute='_compute_risk_last_update',
        # store=True,
    )

    invoiceable_amount = fields.Monetary(
       related="sale_order_id.invoiceable_amount",
       readonly=True,
       store=True,
    )

    invoicing_mode = fields.Selection(related="sale_order_id.invoicing_mode", readonly=True)

    last_summary_date = fields.Datetime(string="Last Summary Date", compute="_compute_last_create_date", readonly=True)

    subscription_count = fields.Integer(compute='_compute_subscription_count')

    date_start = fields.Date(
        string='Start Date',
        compute='_compute_dates',)

    date = fields.Date(
        string='Expiration Date',
        compute='_compute_dates',
        index=True,
        track_visibility='onchange',
    ) 
    
    # accounting fields for legacy integration
    external_account = fields.Char(
        default="/",
    )

    sharepoint_folder = fields.Char(
        string='Sharepoint Folder',
        compute='_compute_sharepoint_folder',
        readonly=True,
        store=True,
    )

    show_folder_path = fields.Boolean()

    @api.depends('sale_order_id', 'project_type', 'parent_id','partner_id','sale_order_id.internal_ref')
    def _compute_sharepoint_folder(self):
        pre = self.env.ref('vcls-contact.SP_client_root_prefix').value
        post = self.env.ref('vcls-contact.SP_client_root_postfix').value
        for project in self.filtered(lambda p: p.project_type=='client' and p.sale_order_id and p.partner_id.altname):
            reference = project.parent_id.sale_order_id.internal_ref if project.parent_id \
                else project.sale_order_id.internal_ref or ''
            if reference:
                reference = "{}-{}".format(reference.split('-')[0],reference.split('-')[1])
            project.sharepoint_folder = "{}/{}/{}/{}{}".format(pre,project.partner_id.altname[0],project.partner_id.altname,reference,post)
            project.show_folder_path = True
    
    def _compute_dates(self):
        for project in self:
            tasks = project.task_ids.filtered(lambda t: t.date_start and t.date_end)
            if tasks:
                project.date_start = min(tasks.mapped('date_start')).date()
                project.date = max(tasks.mapped('date_end')).date()
            elif project.sale_order_id:
                project.date_start = project.sale_order_id.expected_start_date
                project.date = project.sale_order_id.expected_end_date
            else:
                project.date_start = False
                project.date = False


    def _compute_subscription_count(self):
        """Compute the number of distinct subscriptions linked to the orders of the project(s)."""
        for project in self:
            all_projects = project
            if project.child_id:
                all_projects |= project.child_id
            if project.parent_id:
                all_projects |= project.parent_id

            sub_count = len(self.env['sale.order.line'].read_group([('order_id', 'in', all_projects.mapped('sale_order_id.id')), ('subscription_id', '!=', False)],
                                                    ['subscription_id'], ['subscription_id']))
            project.subscription_count = sub_count
    
    def action_open_subscriptions(self):
        """Display the linked subscription and adapt the view to the number of records to display."""
        self.ensure_one()

        all_projects = self
        if self.child_id:
            all_projects |= self.child_id
        if self.parent_id:
            all_projects |= self.parent_id

        subscriptions = all_projects.mapped('sale_order_id.order_line.subscription_id')

        action = self.env.ref('sale_subscription.sale_subscription_action').read()[0]
        if len(subscriptions) > 1:
            action['domain'] = [('id', 'in', subscriptions.ids)]
        elif len(subscriptions) == 1:
            form_view = [(self.env.ref('sale_subscription.sale_subscription_view_form').id, 'form')]
            if 'views' in action:
                action['views'] = form_view + [(state,view) for state,view in action['views'] if view != 'form']
            else:
                action['views'] = form_view
            action['res_id'] = subscriptions.ids[0]
        else:
            action = {'type': 'ir.actions.act_window_close'}
        return action

    @api.depends('summary_ids')
    def _compute_last_create_date(self):
        for project in self:
            if project.summary_ids:
                project.last_summary_date = project.summary_ids.sorted(lambda s: s.create_date, reverse=True)[0].create_date

    @api.depends('sale_order_id.risk_ids')
    def _compute_risk_ids(self):
        for project in self:
            project.risk_ids = self.env['risk'].search([
                ('resource', '=', 'project.project,{}'.format(project.id)),('risk_level', '>', 0)
            ])

    @api.depends('risk_ids', 'risk_ids.score')
    def _compute_risk_score(self):
        for project in self:
            project.risk_score = sum(project.risk_ids.mapped('score'))

    @api.depends('risk_ids.write_date')
    def _compute_risk_last_update(self):
        for project in self:
            last_one = datetime(1970, 1, 1)
            for individual_risks in project.risk_ids:
                if not last_one or individual_risks.write_date > last_one:
                    last_one = individual_risks.write_date
                elif not last_one or individual_risks.create_date > last_one:
                    last_one = individual_risks.create_date
            if last_one != datetime(1970, 1, 1):
                project.risk_last_update = last_one
            else:
                project.risk_last_update = False


    @api.one
    @api.depends(
        'sale_line_id.order_id.order_line.invoice_lines',
        'tasks.sale_order_id',
    )
    def _get_out_invoice_ids(self):
        # use of sudo here because of the non possibility of lc to read some invoices
        project_sudo = self.sudo()
        out_invoice_ids = project_sudo.mapped('sale_line_id.order_id.invoice_ids') \
            | project_sudo.mapped('tasks.sale_order_id.invoice_ids')
        self.out_invoice_ids = out_invoice_ids
        self.invoices_count = len(out_invoice_ids)

    @api.multi
    @api.depends(
        'sale_order_id', 'sale_order_id.parent_id',
        'sale_order_id.parent_id.project_id'
    )
    def _get_parent_id(self):
        for project in self:
            if project.sale_order_id:
                project.parent_id = project.sale_order_id.parent_id.project_id

    ##################
    # CUSTOM METHODS #
    ##################
    @api.multi
    def get_tasks_not_cancelled_and_completable(self):
        """This function will return all the tasks and subtasks that can be completed and is not cancelled"""
        self.ensure_one()
        all_tasks = self.task_ids
        return all_tasks.filtered(lambda task: task.sale_line_id.product_id.product_tmpl_id.completion_elligible and
                                  task.stage_id.status not in  ['cancelled'])

    @api.multi
    def action_raise_new_invoice(self):
        """This function will trigger the Creation of invoice regrouping all the sale orders."""
        orders = self.env['sale.order']
        for project in self:
            if not project.sale_order_id and project.sale_order_id.invoice_status != 'to invoice':
                raise UserError(_("The selected Sales Order should contain something to invoice."))
            else:
                orders |= project.sale_order_id

        action = self.env.ref('sale.action_view_sale_advance_payment_inv').read()[0]
        action['context'] = {
            'active_ids': orders.ids
        }
        return action

    @api.multi
    def action_raise_new_risk(self):
        self.ensure_one()
        action = self.env.ref('vcls-risk.action_view_risk_wizard').read()[0]
        action['context'] = {
            'default_resource': 'project.project,{}'.format(self.id),
            'new_risk':True,
        }
        return action

    @api.multi
    def show_risks(self):
        self.ensure_one()
        action = self.env.ref('vcls-risk.action_view_risk').read()[0]
        action['domain'] = [('resource', '=', 'project.project,{}'.format(self.id))]
        action['target'] = 'current'
        return action

    @api.multi
    def sale_orders_tree_view(self):
        action = self.env.ref('sale.action_quotations').read()[0]
        action['domain'] = [('project_id', 'child_of', self.id)]
        action['context'] = {}
        return action

    @api.multi
    def invoices_tree_view(self):
        self.ensure_one()
        action = self.env.ref('account.action_invoice_tree1').read()[0]
        action['domain'] = [('id', 'in', self.out_invoice_ids.ids)]
        return action

    @api.multi
    def tasks_tree_view(self):
        action = self.env.ref('project.act_project_project_2_project_task_all').read()[0]
        return action

    @api.multi
    def forecasts_tree_view(self):
        action = self.env.ref('project_forecast.project_forecast_action_from_project').read()[0]
        action['domain'] = [('project_id', 'child_of', self.id)]
        return action

    @api.multi
    def timesheets_tree_view(self):
        action = self.env.ref('hr_timesheet.act_hr_timesheet_line_by_project').read()[0]
        action['domain'] = [('project_id', 'child_of', self.id)]
        return action

    @api.multi
    def report_analysis_tree_view(self):
        action = self.env.ref('vcls-timesheet.project_timesheet_forecast_report_action').read()[0]
        action['domain'] = [('project_id', 'child_of', self.id)]
        action['context'] = {'active_id': self.id, 'active_model': 'project.project'}
        return action

    ###############
    # ORM METHODS #
    ###############
    @api.model
    def create(self, vals):

        #default visibility
        vals['privacy_visibility'] = 'portal'

        #if no ID defined, then increment using the sequence
        if vals.get('external_account','/')=='/':
            vals['external_account'] = self.env['ir.sequence'].next_by_code('seq_customer_project_id')
        
        #we automatically assign the project manager to be the one defined in the core team
        if vals.get('sale_order_id',False):
            so = self.env['sale.order'].browse(vals.get('sale_order_id'))
            vals['scope_of_work'] = so.scope_of_work
            lc = so.core_team_id.lead_consultant
            _logger.info("SO info {}".format(lc.name))
            if lc:
                vals['user_id']=lc.user_id.id


        project = super(Project, self).create(vals)
        ids = project._get_default_type_common()
        project.type_ids = ids if ids else project.type_ids

        if project.project_type != 'client':
            project.privacy_visibility = 'employees'
        
        return project

    @api.multi
    def unlink(self):
        authorized = self.env.user.has_group('vcls_security.group_project_controller') or self.env.user.has_group('vcls_security.group_bd_admin')

        if not authorized:
            raise UserError(_("PROJECT UNLINK | You need to be a member of 'BD Admin' or 'Project Controller' to delete project(s)."))
        
        #we catch all child projects
        projects = self.env['project.project']
        for project in self:
            if project.active:
                raise UserError(_("PROJECT UNLINK | To avoid un-wanted deletion, we don't delete active project(s). Please archive {} 1st").format(project.name))
            else:
                projects |= project
                projects |= project.child_id
        
        #we look for invoices
        invoices = projects.mapped('out_invoice_ids')
        if invoices:
                raise UserError(_("PROJECT UNLINK | We can't delete projects having invoices, please archive instead\n{}").format(invoices.mapped('name')))
        
        for project in projects:
            _logger.info("PROJECT UNLINK | Cleaning {}".format(project.name))
            #we look for timesheets 
            ts = self.env['account.analytic.line'].with_context(active_test=False).search([('project_id','=',project.id)])
            if ts:
                _logger.info("PROJECT UNLINK | Timesheets found {}".format(len(ts)))
                if not self.env.user.has_group('vcls_security.group_project_controller'):
                    raise UserError(_("PROJECT UNLINK | You need to be a member of 'Project Controller' to delete timesheet(s)."))
                ts.unlink()
            #we look for forecasts
            fc = self.env['project.forecast'].with_context(active_test=False).search([('project_id','=',project.id)])
            if fc:
                _logger.info("PROJECT UNLINK | Forecasts found {}".format(len(fc)))
                fc.unlink()
            #we clean the tasks
            tasks = self.env['project.task'].with_context(active_test=False).search([('project_id','=',project.id)])
            if tasks:
                _logger.info("PROJECT UNLINK | Tasks found {}".format(len(tasks)))
                tasks.write({'sale_line_id':False})
                tasks.unlink()
            #we clean the mapping
            if project.sale_line_employee_ids:
                _logger.info("PROJECT UNLINK | SO line mappings {}".format(len(project.sale_line_employee_ids)))
                project.sale_line_employee_ids.unlink()

        return super(Project, self).unlink()
    

    ###################
    # COMPUTE METHODS #
    ###################

    @api.model
    def _default_user_id(self):
        if self.project_type == 'client': 
            if self.sale_order_id:
                return self.sale_order_id.opportunity_id.user_id  
        return self.env.user

    def _compute_parent_task_count(self):
        task_data = self.env['project.task'].read_group([('parent_id','=',False),('project_id', 'in', self.ids), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)], ['project_id'], ['project_id'])
        result = dict((data['project_id'][0], data['project_id_count']) for data in task_data)
        for project in self:
            project.parent_task_count = result.get(project.id, 0)
    
    def _compute_child_task_count(self):
        task_data = self.env['project.task'].read_group([('parent_id','!=',False),('project_id', 'in', self.ids), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)], ['project_id'], ['project_id'])
        result = dict((data['project_id'][0], data['project_id_count']) for data in task_data)
        for project in self:
            project.child_task_count = result.get(project.id, 0)
    
    def _compute_task_count(self):
        read_group_res = self.env['project.task'].read_group([('project_id', 'child_of', self.ids), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)], ['project_id'], ['project_id'])
        group_data = dict((data['project_id'][0], data['project_id_count']) for data in read_group_res)
        for project in self:
            task_count = 0
            for sub_project_id in project.search([('id', 'child_of', project.id)]).ids:
                task_count += group_data.get(sub_project_id, 0)
            project.task_count = task_count

    @api.multi
    @api.depends('task_ids.completion_ratio')
    def compute_project_completion_ratio(self):
        for project in self:
            tasks = project.get_tasks_not_cancelled_and_completable()
            weight_sum = 0
            num_times_weight_factor = 0
            #this weights the tasks complete with the contractual budget
            for task in tasks:
                weight_sum += task.contractual_budget
                num_times_weight_factor += ( task.completion_ratio / 100) * task.contractual_budget
            project.completion_ratio = num_times_weight_factor * 100 / weight_sum if weight_sum > 0 else 0


    @api.multi
    @api.depends('task_ids.consummed_completed_ratio')
    def compute_project_consummed_completed_ratio(self):
        for project in self:
            project.consummed_completed_ratio = (project.budget_consumed / project.completion_ratio) * 100 if project.completion_ratio > 0 else 0


    @api.multi
    def _get_is_project_manager(self):
        for p in self:
            p.is_project_manager = p.user_id == self.env.user

    @api.multi
    def compute_orders_count(self):
        for project in self:
            project.orders_count = self.env['sale.order'].search_count([('project_id', 'child_of', project.id)])

    @api.multi
    def compute_invoices_count(self):
        self.ensure_one()
        projects = self.env['project.project'].search([('id', 'child_of', self.id)])
        sale_orders = projects.mapped('sale_line_id.order_id') | projects.mapped('tasks.sale_order_id')
        invoices = sale_orders.mapped('invoice_ids').filtered(lambda inv: inv.type == 'out_invoice')
        self.invoices_count = len(invoices)

    @api.multi
    def toggle_active(self):
        if any(project.activity_ids for project in self):
            raise ValidationError(_("Its not possible to archive a project while there is still undone activities "))
        super(Project, self).toggle_active()

    @api.model
    def end_project_activities_scheduling(self):
        for project_id in self.search([('active', '=', True), ('parent_id', '=', False)]):
            task_ids = [task for child_id in project_id.child_id + project_id for task in child_id.task_ids]
            if task_ids and all(task.stage_id.status in ("completed", "cancelled") for task in task_ids):
                users_summary = {
                    project_id.partner_id.user_id.id: _('Client Feedback'),
                    project_id.user_id.id: _('End of project form filling'),
                    project_id.partner_id.invoice_admin_id.id: _('Invoicing Closing')
                }
                for user_id, summary in users_summary.items():
                    if user_id:
                        activity_vals = {
                            'user_id': user_id,
                            'summary': summary,
                            'act_type_xmlid': 'mail.mail_activity_data_todo',
                            'automated': True,
                        }
                        project_id.sudo().activity_schedule(**activity_vals)
        return True

    def invoicing_session_done(self):
        """
            Timesheets stage_id from pc_review to invoiceable
        """
        if not self.env.user.has_group('vcls_security.group_project_controller'):
            raise ValidationError(_("You need to have the project controller access right."))
        project_ids = self.browse(self._context.get('active_ids'))
        all_projects = project_ids.filtered(lambda p: p.project_type=='client')
        for project in project_ids:
            all_projects |= project.child_id

        #we update the timesheet limit date to the end of the previous month
        today = fields.Date.today()
        ts_limit_date =  today.replace(day=1) - relativedelta(days=1)
        

        timesheet_ids = self.env['account.analytic.line'].search([('main_project_id', 'in', all_projects.ids),
                                                                 ('stage_id', '=', 'pc_review'),
                                                                 ('date','<=',ts_limit_date)])
        if timesheet_ids:
            #fixed price usecase
            fp_ts = timesheet_ids.filtered(lambda t: t.so_line.order_id.invoicing_mode == 'fixed_price')
            if fp_ts:
                fp_ts.write({'stage_id': 'fixed_price'})
            #t&m usecase
            tm_ts = timesheet_ids.filtered(lambda t: t.so_line.order_id.invoicing_mode == 'tm')
            if tm_ts:
                tm_ts.write({'stage_id': 'invoiceable'}) 
        
        
        if all_projects:
            all_projects.mapped('sale_order_id').write({'timesheet_limit_date':ts_limit_date})


        #we trigger the computation of KPIs
        self.env['project.task']._cron_compute_kpi()

    @api.multi
    def write(self, values):
        if values.get('active', None) is False:
            tasks = self.mapped('tasks')
            tasks |= tasks.mapped('child_ids')
            tasks.write({'active': False})
        return super(Project, self).write(values)

    @api.multi
    def _get_family_project_ids(self):
        self.ensure_one()
        parent_project_id = self.parent_id or self
        family_project_ids = parent_project_id | parent_project_id.child_id
        return family_project_ids
    
    @api.model
    def detect_bad_tasks(self):
        projects = self.search([('project_type','=','client')])
        tag = self.env.ref('vcls-project.proj_tag_bad_task')
        for project in projects:
            #we get authorized parent taks from the sale_order
            authorized_tasks = project.sale_order_id.order_line.mapped('task_id')
            to_update = self.env['project.task']
            bad = project.task_ids.filtered(lambda t: (t.id not in authorized_tasks.ids) and not t.parent_id)
            for task in bad:
                to_update |= task | task.child_ids
            if bad:
                _logger.info("Bad Tasks in {} | {}".format(project.name,to_update.mapped('name')))
                to_update.write({
                    'tag_ids':[(4,tag.id,0)],
                })

    """@api.onchange('sale_line_employee_ids')
Exemple #5
0
class Contract(models.Model):

    _inherit = 'hr.contract'

    hourly_wage = fields.Monetary(digits=(16, 2), track_visibility="onchange")
Exemple #6
0
class PerformanceSales(models.Model):
     _name = 'performance.sales'
     _description = 'Performance Sales'
     _inherit = 'performance.mixin'

     currency_id = fields.Many2one(
          comodel_name = 'res.currency',
          readonly = True,
     )

     #Objectives
     sales_objective_period = fields.Monetary()
     sales_objective_cumulative = fields.Monetary(
          compute = '_compute_sales_objective_cumulative',
          store = True,
     )

     @api.depends('sales_objective_period')
     def _compute_sales_objective_cumulative(self):
          for perf in self:
               perf.sales_objective_cumulative = perf.sales_objective_period*(perf.period_index+1)

     #Realized Sales
     sales_new_period = fields.Monetary(readonly=True) #OK
     sales_new_cumulative = fields.Monetary(readonly=True) #OK
     sales_retained_period = fields.Monetary(readonly=True) #OK
     sales_retained_cumulative = fields.Monetary(readonly=True) #OK
     sales_total_period = fields.Monetary(readonly=True) #OK
     sales_total_cumulative = fields.Monetary(readonly=True) #OK

     sales_count_new_period = fields.Integer(readonly=True) #OK
     sales_count_new_cumulative = fields.Integer(readonly=True) #OK
     sales_count_retained_period = fields.Integer(readonly=True) #OK
     sales_count_retained_cumulative = fields.Integer(readonly=True) #OK
     sales_count_total_period = fields.Integer(readonly=True) #OK
     sales_count_total_cumulative = fields.Integer(readonly=True) #OK

     #Realized Losses
     losses_new_period = fields.Monetary(readonly=True) #OK
     losses_new_cumulative = fields.Monetary(readonly=True) #OK
     losses_retained_period = fields.Monetary(readonly=True) #OK
     losses_retained_cumulative = fields.Monetary(readonly=True) #OK
     losses_total_period = fields.Monetary(readonly=True) #OK
     losses_total_cumulative = fields.Monetary(readonly=True) #OK

     losses_count_new_period = fields.Integer(readonly=True) #OK
     losses_count_new_cumulative = fields.Integer(readonly=True) #OK
     losses_count_retained_period = fields.Integer(readonly=True) #OK
     losses_count_retained_cumulative = fields.Integer(readonly=True) #OK
     losses_count_total_period = fields.Integer(readonly=True) #OK
     losses_count_total_cumulative = fields.Integer(readonly=True) #OK

     #ratios
     win_loss_count_new_period = fields.Float(readonly=True) #OK
     win_loss_count_new_cumulative = fields.Float(readonly=True) #OK
     win_loss_count_retained_period = fields.Float(readonly=True) #OK
     win_loss_count_retained_cumulative = fields.Float(readonly=True) #OK
     win_loss_count_total_period = fields.Float(readonly=True) #OK
     win_loss_count_total_cumulative = fields.Float(readonly=True) #OK

     win_loss_new_period = fields.Float(readonly=True) #OK
     win_loss_new_cumulative = fields.Float(readonly=True) #OK
     win_loss_retained_period = fields.Float(readonly=True) #OK
     win_loss_retained_cumulative = fields.Float(readonly=True) #OK
     win_loss_total_period = fields.Float(readonly=True) #OK
     win_loss_total_cumulative = fields.Float(readonly=True) #OK

     #pipeline
     pipe_new_period = fields.Monetary(readonly=True) #OK
     pipe_new_period_weighted = fields.Monetary(readonly=True) #OK
     pipe_retained_period = fields.Monetary(readonly=True) #OK
     pipe_retained_period_weighted = fields.Monetary(readonly=True) #OK
     pipe_total_period = fields.Monetary(readonly=True) #OK
     pipe_total_period_weighted = fields.Monetary(readonly=True) #OK
     pipe_count_new_period = fields.Integer(readonly=True) #OK
     pipe_count_retained_period = fields.Integer(readonly=True) #OK
     pipe_count_total_period = fields.Integer(readonly=True) #OK

     pipe_new_cumulative = fields.Monetary(readonly=True) #OK
     pipe_new_cumulative_weighted = fields.Monetary(readonly=True)  #OK
     pipe_retained_cumulative = fields.Monetary(readonly=True)  #OK
     pipe_retained_cumulative_weighted = fields.Monetary(readonly=True)  #OK
     pipe_total_cumulative = fields.Monetary(readonly=True)  #OK
     pipe_total_cumulative_weighted = fields.Monetary(readonly=True)  #OK
     pipe_count_new_cumulative = fields.Integer(readonly=True) #OK
     pipe_count_retained_cumulative = fields.Integer(readonly=True) #OK
     pipe_count_total_cumulative = fields.Integer(readonly=True) #OK

     #active snapshot
     active_count_new = fields.Integer(readonly=True) #OK
     active_count_retained = fields.Integer(readonly=True) #OK
     active_count_total = fields.Integer(readonly=True) #OK

     active_new = fields.Monetary(readonly=True) #OK
     active_retained = fields.Monetary(readonly=True) #OK
     active_total = fields.Monetary(readonly=True) #OK

     active_new_weighted = fields.Monetary(readonly=True) #OK
     active_retained_weighted = fields.Monetary(readonly=True) #OK
     active_total_weighted = fields.Monetary(readonly=True) #OK
 
     def _compute_active_snapshot(self):
          today = fields.Date.today()
          for perf in self.filtered(lambda p: p.date_start<=today and p.date_end>=today):
               open_sos = self.env['sale.order'].search([
                    ('company_id','=',perf.company_id.id),
                    ('sale_status','in',['draft','sent'])])
               
               perf.active_new = sum(open_sos.filtered(lambda s: s.sale_profile == 'new').mapped('converted_untaxed_amount'))
               perf.active_retained = sum(open_sos.filtered(lambda s: s.sale_profile == 'retained').mapped('converted_untaxed_amount'))
               perf.active_total = perf.active_new + perf.active_retained
               perf.active_count_new = len(open_sos.filtered(lambda s: s.sale_profile == 'new'))
               perf.active_count_retained = len(open_sos.filtered(lambda s: s.sale_profile == 'retained'))
               perf.active_count_total = perf.active_count_new + perf.active_count_retained

               perf.active_new_weighted = sum(open_sos.filtered(lambda s: s.sale_profile == 'new').mapped(lambda p: (p.converted_untaxed_amount*p.probability)/100))
               perf.active_retained_weighted = sum(open_sos.filtered(lambda s: s.sale_profile == 'retained').mapped(lambda p: (p.converted_untaxed_amount*p.probability)/100))
               perf.active_total_weighted = perf.active_new_weighted + perf.active_retained_weighted

     def _compute_period_sales(self):
          ordered_perf = self.sorted(lambda p: (p.period_id.date_start,p.period_index))
          for perf in ordered_perf:
               _logger.info("PERF | Period Sales Calculation | {}-{} index {}-{}".format(perf.period_id.name,perf.period_id.date_start,perf.period_index,perf.date_start))
               #we get the relevant sale.order
               sos = self.env['sale.order'].search([
                    ('company_id','=',perf.company_id.id),
                    ('sales_reporting_date','>=',perf.date_start),
                    ('sales_reporting_date','<=',perf.date_end),
                    ('sale_status','not in',['cancel'])])
               
               new_sos = sos.filtered(lambda p: p.sale_profile=='new')
               retained_sos = sos.filtered(lambda p: p.sale_profile=='retained')
               _logger.info("PERF | Found {} SO in period {}\n{} NEW and {} RETAINED".format(len(sos),perf.date_start,len(new_sos),len(retained_sos)))

               #sales & losses
               perf.sales_new_period = sum(new_sos.filtered(lambda s: s.sale_status == 'won').mapped('converted_untaxed_amount'))
               perf.sales_retained_period = sum(retained_sos.filtered(lambda s: s.sale_status == 'won').mapped('converted_untaxed_amount'))
               perf.sales_total_period  = perf.sales_new_period + perf.sales_retained_period

               perf.sales_count_new_period = len(new_sos.filtered(lambda s: s.sale_status == 'won'))
               perf.sales_count_retained_period = len(retained_sos.filtered(lambda s: s.sale_status == 'won'))
               perf.sales_count_total_period  = perf.sales_count_new_period + perf.sales_count_retained_period

               perf.losses_new_period = sum(new_sos.filtered(lambda s: s.sale_status == 'lost').mapped('converted_untaxed_amount'))
               perf.losses_retained_period = sum(retained_sos.filtered(lambda s: s.sale_status == 'lost').mapped('converted_untaxed_amount'))
               perf.losses_total_period  = perf.losses_new_period + perf.losses_retained_period

               perf.losses_count_new_period = len(new_sos.filtered(lambda s: s.sale_status == 'lost'))
               perf.losses_count_retained_period = len(retained_sos.filtered(lambda s: s.sale_status == 'lost'))
               perf.losses_count_total_period  = perf.losses_count_new_period + perf.losses_count_retained_period

               #ratios
               if (perf.sales_count_new_period + perf.losses_new_period) > 0:
                    perf.win_loss_count_new_period = perf.sales_count_new_period / (perf.sales_count_new_period + perf.losses_count_new_period)
                    perf.win_loss_new_period = perf.sales_new_period / (perf.sales_new_period + perf.losses_new_period)
               else:
                    perf.win_loss_count_new_period = False
                    perf.win_loss_new_period = False
               
               if (perf.sales_count_retained_period + perf.losses_retained_period) > 0:
                    perf.win_loss_count_retained_period = perf.sales_count_retained_period / (perf.sales_count_retained_period + perf.losses_count_retained_period)
                    perf.win_loss_retained_period = perf.sales_retained_period / (perf.sales_retained_period + perf.losses_retained_period)
               else:
                    perf.win_loss_count_retained_period = False
                    perf.win_loss_retained_period = False
               
               if  (perf.sales_total_period + perf.losses_total_period) > 0:
                    perf.win_loss_total_period = perf.sales_total_period / (perf.sales_total_period + perf.losses_total_period)
               else:
                    perf.win_loss_total_period = False
               
               #pipeline
               perf.pipe_new_period = sum(new_sos.filtered(lambda s: s.sale_status in ['draft','sent']).mapped('converted_untaxed_amount'))
               perf.pipe_new_period_weighted = sum(new_sos.filtered(lambda s: s.sale_status in ['draft','sent']).mapped(lambda p: (p.converted_untaxed_amount*p.probability)/100))
               perf.pipe_count_new_period = len(new_sos.filtered(lambda s: s.sale_status in ['draft','sent']))

               perf.pipe_retained_period = sum(retained_sos.filtered(lambda s: s.sale_status in ['draft','sent']).mapped('converted_untaxed_amount'))
               perf.pipe_retained_period_weighted = sum(retained_sos.filtered(lambda s: s.sale_status in ['draft','sent']).mapped(lambda p: (p.converted_untaxed_amount*p.probability)/100))
               perf.pipe_count_retained_period = len(retained_sos.filtered(lambda s: s.sale_status in ['draft','sent']))

               perf.pipe_total_period = perf.pipe_new_period + perf.pipe_retained_period
               perf.pipe_total_period_weighted = perf.pipe_new_period_weighted + perf.pipe_retained_period_weighted
               perf.pipe_count_total_period = perf.pipe_count_new_period + perf.pipe_count_retained_period
     
     def _compute_cumulative_sales(self):
          #we order per period and index
          c_fields = [
               'sales_new_cumulative',
               'sales_retained_cumulative',
               'sales_total_cumulative',
               'sales_count_new_cumulative',
               'sales_count_retained_cumulative',
               'sales_count_total_cumulative',
               'losses_new_cumulative',
               'losses_retained_cumulative',
               'losses_total_cumulative',
               'losses_count_new_cumulative',
               'losses_count_retained_cumulative',
               'losses_count_total_cumulative',
               'pipe_new_cumulative',
               'pipe_new_cumulative_weighted', 
               'pipe_retained_cumulative', 
               'pipe_retained_cumulative_weighted', 
               'pipe_total_cumulative', 
               'pipe_total_cumulative_weighted', 
               'pipe_count_new_cumulative',
               'pipe_count_retained_cumulative',
               'pipe_count_total_cumulative',
               ]
          
          p_fields = [f.replace('cumulative','period') for f in c_fields]

          ordered_perf = self.sorted(lambda p: (p.period_id.date_start,p.period_index))
          for perf in ordered_perf:
               _logger.info("PERF | Cumulative Sales Calculation | {}-{} index {}-{}".format(perf.period_id.name,perf.period_id.date_start,perf.period_index,perf.date_start))
               #prev_perfs = self.search([('period_id','=',perf.period_id.id),('period_index','<=',perf.period_index)])
               last_perf = self.search([('period_id','=',perf.period_id.id),('period_index','=',perf.period_index-1)],limit=1)
               if last_perf:
                    last_data = last_perf.read(c_fields)[0]
               else:
                    last_data = {field_name:0 for field_name in c_fields}
               current_data = perf.read(p_fields)[0]
               _logger.info("PERF | \nLast Data {}\nCurrent Data {}".format(last_data,current_data))

               vals = {}
               for field in c_fields:
                    vals.update({field: last_data[field] + current_data[field.replace('cumulative','period')]})
               
               _logger.info("PERF | New Values {}".format(vals))
               perf.write(vals)

               #ratios
               if (perf.sales_count_new_cumulative + perf.losses_new_cumulative) > 0:
                    perf.win_loss_count_new_cumulative = perf.sales_count_new_cumulative / (perf.sales_count_new_cumulative + perf.losses_count_new_cumulative)
                    perf.win_loss_new_cumulative = perf.sales_new_cumulative / (perf.sales_new_cumulative + perf.losses_new_cumulative)
               else:
                    perf.win_loss_count_new_cumulative = False
                    perf.win_loss_new_cumulative = False
               
               if (perf.sales_count_retained_cumulative + perf.losses_retained_cumulative) > 0:
                    perf.win_loss_count_retained_cumulative = perf.sales_count_retained_cumulative / (perf.sales_count_retained_cumulative + perf.losses_count_retained_cumulative)
                    perf.win_loss_retained_cumulative = perf.sales_retained_cumulative / (perf.sales_retained_cumulative + perf.losses_retained_cumulative)
               else:
                    perf.win_loss_count_retained_cumulative = False
                    perf.win_loss_retained_cumulative = False
               
               if  (perf.sales_total_cumulative + perf.losses_total_cumulative) > 0:
                    perf.win_loss_total_cumulative = perf.sales_total_cumulative / (perf.sales_total_cumulative + perf.losses_total_cumulative)
               else:
                    perf.win_loss_total_cumulative = False
Exemple #7
0
class ResPartner(models.Model):
    _name = 'res.partner'
    _inherit = 'res.partner'

    @api.depends_context('company')
    def _credit_debit_get(self):
        tables, where_clause, where_params = self.env[
            'account.move.line'].with_context(
                state='posted', company_id=self.env.company.id)._query_get()
        where_params = [tuple(self.ids)] + where_params
        if where_clause:
            where_clause = 'AND ' + where_clause
        self._cr.execute(
            """SELECT account_move_line.partner_id, act.type, SUM(account_move_line.amount_residual)
                      FROM """ + tables + """
                      LEFT JOIN account_account a ON (account_move_line.account_id=a.id)
                      LEFT JOIN account_account_type act ON (a.user_type_id=act.id)
                      WHERE act.type IN ('receivable','payable')
                      AND account_move_line.partner_id IN %s
                      AND account_move_line.reconciled IS NOT TRUE
                      """ + where_clause + """
                      GROUP BY account_move_line.partner_id, act.type
                      """, where_params)
        treated = self.browse()
        for pid, type, val in self._cr.fetchall():
            partner = self.browse(pid)
            if type == 'receivable':
                partner.credit = val
                if partner not in treated:
                    partner.debit = False
                    treated |= partner
            elif type == 'payable':
                partner.debit = -val
                if partner not in treated:
                    partner.credit = False
                    treated |= partner
        remaining = (self - treated)
        remaining.debit = False
        remaining.credit = False

    def _asset_difference_search(self, account_type, operator, operand):
        if operator not in ('<', '=', '>', '>=', '<='):
            return []
        if type(operand) not in (float, int):
            return []
        sign = 1
        if account_type == 'payable':
            sign = -1
        res = self._cr.execute(
            '''
            SELECT partner.id
            FROM res_partner partner
            LEFT JOIN account_move_line aml ON aml.partner_id = partner.id
            JOIN account_move move ON move.id = aml.move_id
            RIGHT JOIN account_account acc ON aml.account_id = acc.id
            WHERE acc.internal_type = %s
              AND NOT acc.deprecated AND acc.company_id = %s
              AND move.state = 'posted'
            GROUP BY partner.id
            HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator +
            ''' %s''', (account_type, self.env.company.id, sign, operand))
        res = self._cr.fetchall()
        if not res:
            return [('id', '=', '0')]
        return [('id', 'in', [r[0] for r in res])]

    @api.model
    def _credit_search(self, operator, operand):
        return self._asset_difference_search('receivable', operator, operand)

    @api.model
    def _debit_search(self, operator, operand):
        return self._asset_difference_search('payable', operator, operand)

    def _invoice_total(self):
        self.total_invoiced = 0
        if not self.ids:
            return True

        all_partners_and_children = {}
        all_partner_ids = []
        for partner in self.filtered('id'):
            # price_total is in the company currency
            all_partners_and_children[partner] = self.with_context(
                active_test=False).search([('id', 'child_of', partner.id)]).ids
            all_partner_ids += all_partners_and_children[partner]

        domain = [
            ('partner_id', 'in', all_partner_ids),
            ('state', 'not in', ['draft', 'cancel']),
            ('move_type', 'in', ('out_invoice', 'out_refund')),
        ]
        price_totals = self.env['account.invoice.report'].read_group(
            domain, ['price_subtotal'], ['partner_id'])
        for partner, child_ids in all_partners_and_children.items():
            partner.total_invoiced = sum(
                price['price_subtotal'] for price in price_totals
                if price['partner_id'][0] in child_ids)

    def _compute_journal_item_count(self):
        AccountMoveLine = self.env['account.move.line']
        for partner in self:
            partner.journal_item_count = AccountMoveLine.search_count([
                ('partner_id', '=', partner.id)
            ])

    def _compute_has_unreconciled_entries(self):
        for partner in self:
            # Avoid useless work if has_unreconciled_entries is not relevant for this partner
            if not partner.active or not partner.is_company and partner.parent_id:
                partner.has_unreconciled_entries = False
                continue
            self.env.cr.execute(
                """ SELECT 1 FROM(
                        SELECT
                            p.last_time_entries_checked AS last_time_entries_checked,
                            MAX(l.write_date) AS max_date
                        FROM
                            account_move_line l
                            RIGHT JOIN account_account a ON (a.id = l.account_id)
                            RIGHT JOIN res_partner p ON (l.partner_id = p.id)
                        WHERE
                            p.id = %s
                            AND EXISTS (
                                SELECT 1
                                FROM account_move_line l
                                WHERE l.account_id = a.id
                                AND l.partner_id = p.id
                                AND l.amount_residual > 0
                            )
                            AND EXISTS (
                                SELECT 1
                                FROM account_move_line l
                                WHERE l.account_id = a.id
                                AND l.partner_id = p.id
                                AND l.amount_residual < 0
                            )
                        GROUP BY p.last_time_entries_checked
                    ) as s
                    WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked)
                """, (partner.id, ))
            partner.has_unreconciled_entries = self.env.cr.rowcount == 1

    def mark_as_reconciled(self):
        self.env['account.partial.reconcile'].check_access_rights('write')
        return self.sudo().write({
            'last_time_entries_checked':
            time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
        })

    def _get_company_currency(self):
        for partner in self:
            if partner.company_id:
                partner.currency_id = partner.sudo().company_id.currency_id
            else:
                partner.currency_id = self.env.company.currency_id

    credit = fields.Monetary(compute='_credit_debit_get',
                             search=_credit_search,
                             string='Total Receivable',
                             help="Total amount this customer owes you.")
    debit = fields.Monetary(
        compute='_credit_debit_get',
        search=_debit_search,
        string='Total Payable',
        help="Total amount you have to pay to this vendor.")
    debit_limit = fields.Monetary('Payable Limit')
    total_invoiced = fields.Monetary(
        compute='_invoice_total',
        string="Total Invoiced",
        groups='account.group_account_invoice,account.group_account_readonly')
    currency_id = fields.Many2one(
        'res.currency',
        compute='_get_company_currency',
        readonly=True,
        string="Currency",
        help='Utility field to express amount currency')
    journal_item_count = fields.Integer(compute='_compute_journal_item_count',
                                        string="Journal Items")
    property_account_payable_id = fields.Many2one(
        'account.account',
        company_dependent=True,
        string="Account Payable",
        domain=
        "[('internal_type', '=', 'payable'), ('deprecated', '=', False), ('company_id', '=', current_company_id)]",
        help=
        "This account will be used instead of the default one as the payable account for the current partner",
        required=True)
    property_account_receivable_id = fields.Many2one(
        'account.account',
        company_dependent=True,
        string="Account Receivable",
        domain=
        "[('internal_type', '=', 'receivable'), ('deprecated', '=', False), ('company_id', '=', current_company_id)]",
        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",
        domain="[('company_id', '=', current_company_id)]",
        help=
        "The fiscal position determines the taxes/accounts used for this contact."
    )
    property_payment_term_id = fields.Many2one(
        'account.payment.term',
        company_dependent=True,
        string='Customer Payment Terms',
        domain="[('company_id', 'in', [current_company_id, False])]",
        help=
        "This payment term will be used instead of the default one for sales orders and customer invoices"
    )
    property_supplier_payment_term_id = fields.Many2one(
        'account.payment.term',
        company_dependent=True,
        string='Vendor Payment Terms',
        domain="[('company_id', 'in', [current_company_id, False])]",
        help=
        "This payment term will be used instead of the default one for purchase orders and vendor bills"
    )
    ref_company_ids = fields.One2many(
        'res.company', 'partner_id', string='Companies that refers to partner')
    has_unreconciled_entries = fields.Boolean(
        compute='_compute_has_unreconciled_entries',
        help=
        "The partner has at least one unreconciled debit and credit since last time the invoices & payments matching was performed."
    )
    last_time_entries_checked = fields.Datetime(
        string='Latest Invoices & Payments Matching Date',
        readonly=True,
        copy=False,
        help=
        'Last time the invoices & payments matching was performed for this partner. '
        'It is set either if there\'s not at least an unreconciled debit and an unreconciled credit '
        'or if you click the "Done" button.')
    invoice_ids = fields.One2many('account.move',
                                  'partner_id',
                                  string='Invoices',
                                  readonly=True,
                                  copy=False)
    contract_ids = fields.One2many('account.analytic.account',
                                   'partner_id',
                                   string='Partner Contracts',
                                   readonly=True)
    bank_account_count = fields.Integer(compute='_compute_bank_count',
                                        string="Bank")
    trust = fields.Selection([('good', 'Good Debtor'),
                              ('normal', 'Normal Debtor'),
                              ('bad', 'Bad Debtor')],
                             string='Degree of trust you have in this debtor',
                             default='normal',
                             company_dependent=True)
    invoice_warn = fields.Selection(WARNING_MESSAGE,
                                    'Invoice',
                                    help=WARNING_HELP,
                                    default="no-message")
    invoice_warn_msg = fields.Text('Message for Invoice')
    # Computed fields to order the partners as suppliers/customers according to the
    # amount of their generated incoming/outgoing account moves
    supplier_rank = fields.Integer(default=0, copy=False)
    customer_rank = fields.Integer(default=0, copy=False)

    duplicated_bank_account_partners_count = fields.Integer(
        compute='_compute_duplicated_bank_account_partners_count',
        help=
        'Technical field holding the amount partners that share the same account number as any set on this partner.',
    )

    def _get_name_search_order_by_fields(self):
        res = super()._get_name_search_order_by_fields()
        partner_search_mode = self.env.context.get('res_partner_search_mode')
        if not partner_search_mode in ('customer', 'supplier'):
            return res
        order_by_field = 'COALESCE(res_partner.%s, 0) DESC,'
        if partner_search_mode == 'customer':
            field = 'customer_rank'
        else:
            field = 'supplier_rank'

        order_by_field = order_by_field % field
        return '%s, %s' % (res,
                           order_by_field % field) if res else order_by_field

    def _compute_bank_count(self):
        bank_data = self.env['res.partner.bank'].read_group(
            [('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id'])
        mapped_data = dict([(bank['partner_id'][0], bank['partner_id_count'])
                            for bank in bank_data])
        for partner in self:
            partner.bank_account_count = mapped_data.get(partner.id, 0)

    def _get_duplicated_bank_accounts(self):
        self.ensure_one()
        if not self.bank_ids:
            return self.env['res.partner.bank']
        domains = []
        for bank in self.bank_ids:
            domains.append([('acc_number', '=', bank.acc_number),
                            ('bank_id', '=', bank.bank_id.id)])
        domain = expression.OR(domains)
        if self.company_id:
            domain = expression.AND(
                [domain, [('company_id', 'in', (False, self.company_id.id))]])
        domain = expression.AND(
            [domain, [('partner_id', '!=', self._origin.id)]])
        return self.env['res.partner.bank'].search(domain)

    @api.depends('bank_ids')
    def _compute_duplicated_bank_account_partners_count(self):
        for partner in self:
            partner.duplicated_bank_account_partners_count = len(
                partner._get_duplicated_bank_accounts())

    def _find_accounting_partner(self, partner):
        ''' Find the partner for which the accounting entries will be created '''
        return partner.commercial_partner_id

    @api.model
    def _commercial_fields(self):
        return super(ResPartner, self)._commercial_fields() + \
            ['debit_limit', 'property_account_payable_id', 'property_account_receivable_id', 'property_account_position_id',
             'property_payment_term_id', 'property_supplier_payment_term_id', 'last_time_entries_checked']

    def action_view_partner_invoices(self):
        self.ensure_one()
        action = self.env["ir.actions.actions"]._for_xml_id(
            "account.action_move_out_invoice_type")
        action['domain'] = [
            ('move_type', 'in', ('out_invoice', 'out_refund')),
            ('partner_id', 'child_of', self.id),
        ]
        action['context'] = {
            'default_move_type': 'out_invoice',
            'move_type': 'out_invoice',
            'journal_type': 'sale',
            'search_default_open': 1
        }
        return action

    def action_view_partner_with_same_bank(self):
        self.ensure_one()
        partners = self._get_duplicated_bank_accounts()
        # Open a list view or form view of the partner(s) with the same bank accounts
        if self.duplicated_bank_account_partners_count == 1:
            action_vals = {
                'type': 'ir.actions.act_window',
                'res_model': 'res.partner',
                'view_mode': 'form',
                'res_id': partners.id,
                'views': [(False, 'form')],
            }
        else:
            action_vals = {
                'name': _("Partners"),
                'type': 'ir.actions.act_window',
                'res_model': 'res.partner',
                'view_mode': 'tree,form',
                'views': [(False, 'list'), (False, 'form')],
                'domain': [('id', 'in', partners.ids)],
            }

        return action_vals

    def can_edit_vat(self):
        ''' Can't edit `vat` if there is (non draft) issued invoices. '''
        can_edit_vat = super(ResPartner, self).can_edit_vat()
        if not can_edit_vat:
            return can_edit_vat
        has_invoice = self.env['account.move'].search(
            [('move_type', 'in', ['out_invoice', 'out_refund']),
             ('partner_id', 'child_of', self.commercial_partner_id.id),
             ('state', '=', 'posted')],
            limit=1)
        return can_edit_vat and not (bool(has_invoice))

    @api.model_create_multi
    def create(self, vals_list):
        search_partner_mode = self.env.context.get('res_partner_search_mode')
        is_customer = search_partner_mode == 'customer'
        is_supplier = search_partner_mode == 'supplier'
        if search_partner_mode:
            for vals in vals_list:
                if is_customer and 'customer_rank' not in vals:
                    vals['customer_rank'] = 1
                elif is_supplier and 'supplier_rank' not in vals:
                    vals['supplier_rank'] = 1
        return super().create(vals_list)

    def _increase_rank(self, field, n=1):
        if self.ids and field in ['customer_rank', 'supplier_rank']:
            try:
                with self.env.cr.savepoint(flush=False):
                    query = sql.SQL("""
                        SELECT {field} FROM res_partner WHERE ID IN %(partner_ids)s FOR UPDATE NOWAIT;
                        UPDATE res_partner SET {field} = {field} + %(n)s
                        WHERE id IN %(partner_ids)s
                    """).format(field=sql.Identifier(field))
                    self.env.cr.execute(query, {
                        'partner_ids': tuple(self.ids),
                        'n': n
                    })
                    for partner in self:
                        self.env.cache.remove(partner, partner._fields[field])
            except DatabaseError as e:
                if e.pgcode == '55P03':
                    _logger.debug(
                        'Another transaction already locked partner rows. Cannot update partner ranks.'
                    )
                else:
                    raise e
Exemple #8
0
class ResCompany(models.Model):
    _inherit = "res.company"

    @api.multi
    def _compute_l10n_br_data(self):
        """ Read the l10n_br specific functional fields. """
        super(ResCompany, self)._compute_l10n_br_data()
        for c in self:
            c.tax_framework = c.partner_id.tax_framework
            c.cnae_main_id = c.partner_id.cnae_main_id

    def _inverse_cnae_main_id(self):
        """ Write the l10n_br specific functional fields. """
        for c in self:
            c.partner_id.cnae_main_id = c.cnae_main_id

    def _inverse_tax_framework(self):
        """ Write the l10n_br specific functional fields. """
        for c in self:
            c.partner_id.tax_framework = c.tax_framework

    @api.depends("simplifed_tax_id", "annual_revenue")
    def _compute_simplifed_tax_range(self):
        for record in self:
            tax_range = record.env["l10n_br_fiscal.simplified.tax.range"].search(
                [
                    ("inital_revenue", "<=", record.annual_revenue),
                    ("final_revenue", ">=", record.annual_revenue),
                ],
                limit=1,
            )

            if tax_range:
                record.simplifed_tax_range_id = tax_range.id

    cnae_main_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.cnae",
        compute="_compute_l10n_br_data",
        inverse="_inverse_cnae_main_id",
        domain="[('internal_type', '=', 'normal'), "
        "('id', 'not in', cnae_secondary_ids)]",
        string="Main CNAE")

    cnae_secondary_ids = fields.Many2many(
        comodel_name="l10n_br_fiscal.cnae",
        relation="res_company_fiscal_cnae_rel",
        colunm1="company_id",
        colunm2="cnae_id",
        domain="[('internal_type', '=', 'normal'), "
               "('id', '!=', cnae_main_id)]",
        string="Secondary CNAE")

    tax_framework = fields.Selection(
        selection=TAX_FRAMEWORK,
        default=TAX_FRAMEWORK_NORMAL,
        compute="_compute_l10n_br_data",
        inverse="_inverse_tax_framework",
        string="Tax Framework")

    profit_calculation = fields.Selection(
        selection=PROFIT_CALCULATION,
        default=PROFIT_CALCULATION_PRESUMED,
        string="Profit Calculation")

    is_industry = fields.Boolean(
        string="Is Industry",
        help="If your company is industry or ......",
        default=False)

    industry_type = fields.Selection(
        selection=INDUSTRY_TYPE,
        default=INDUSTRY_TYPE_TRANSFORMATION,
        string="Industry Type")

    annual_revenue = fields.Monetary(
        string="Annual Revenue",
        currency_field="currency_id",
        default=0.00,
        digits=dp.get_precision("Fiscal Documents"))

    simplifed_tax_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.simplified.tax",
        domain="[('cnae_ids', '=', cnae_main_id)]",
        string="Simplified Tax")

    simplifed_tax_range_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.simplified.tax.range",
        domain="[('simplified_tax_id', '=', simplifed_tax_id)]",
        compute="_compute_simplifed_tax_range",
        store=True,
        readyonly=True,
        string="Simplified Tax Range")

    simplifed_tax_percent = fields.Float(
        string="Simplifed Tax Percent",
        default=0.00,
        related="simplifed_tax_range_id.total_tax_percent",
        digits=dp.get_precision("Fiscal Tax Percent"))

    ibpt_api = fields.Boolean(
        string="Use IBPT API",
        default=False)

    ibpt_token = fields.Char(
        string="IBPT Token")

    ibpt_update_days = fields.Integer(
        string="IBPT Token Updates",
        default=15)

    certificate_ecnpj_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.certificate",
        string="E-CNPJ",
        domain="[('type', '=', 'e-cnpj'), ('is_valid', '=', True)]")

    certificate_nfe_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.certificate",
        string="NFe",
        domain="[('type', '=', 'nf-e'), ('is_valid', '=', True)]")

    accountant_id = fields.Many2one(
        comodel_name="res.partner",
        string="Accountant")

    technical_support_id = fields.Many2one(
        comodel_name="res.partner",
        string="Technical Support")

    piscofins_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax.pis.cofins",
        string="PIS/COFINS",
        domain="[('piscofins_type', '=', 'company')]")

    tax_cofins_wh_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default COFINS RET",
        domain=[('tax_domain', '=', TAX_DOMAIN_COFINS_WH)])

    tax_pis_wh_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default PIS RET",
        domain=[('tax_domain', '=', TAX_DOMAIN_PIS_WH)])

    tax_csll_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default CSLL",
        domain=[('tax_domain', '=', TAX_DOMAIN_CSLL)])

    tax_csll_wh_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default CSLL RET",
        domain=[('tax_domain', '=', TAX_DOMAIN_CSLL_WH)])

    ripi = fields.Boolean(
        string="RIPI")

    tax_ipi_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default IPI",
        domain=[("tax_domain", "=", TAX_DOMAIN_IPI)])

    tax_icms_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default ICMS",
        domain=[('tax_domain', 'in', (TAX_DOMAIN_ICMS, TAX_DOMAIN_ICMS_SN))])

    icms_regulation_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.icms.regulation",
        string="ICMS Regulation")

    tax_issqn_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default ISSQN",
        domain=[('tax_domain', '=', TAX_DOMAIN_ISSQN)])

    tax_issqn_wh_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default ISSQN RET",
        domain=[('tax_domain', '=', TAX_DOMAIN_ISSQN_WH)])

    tax_irpj_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default IRPJ",
        domain=[('tax_domain', '=', TAX_DOMAIN_IRPJ)])

    tax_irpj_wh_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default IRPJ RET",
        domain=[('tax_domain', '=', TAX_DOMAIN_IRPJ_WH)])

    tax_inss_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default INSS",
        domain=[('tax_domain', '=', TAX_DOMAIN_INSS)])

    tax_inss_wh_id = fields.Many2one(
        comodel_name="l10n_br_fiscal.tax",
        string="Default INSS RET",
        domain=[('tax_domain', '=',  TAX_DOMAIN_INSS_WH)])

    tax_definition_ids = fields.One2many(
        comodel_name="l10n_br_fiscal.tax.definition",
        inverse_name="company_id",
        string="Tax Definition")

    processador_edoc = fields.Selection(
        selection=PROCESSADOR,
        string='Processador documentos eletrônicos',
        default=PROCESSADOR_NENHUM)

    document_type_id = fields.Many2one(
        comodel_name='l10n_br_fiscal.document.type',
        string='Default Document Type')

    def _del_tax_definition(self, tax_domain):
        tax_def = self.tax_definition_ids.filtered(
            lambda d: d.tax_group_id.tax_domain != tax_domain
        )
        self.tax_definition_ids = tax_def

    def _set_tax_definition(self, tax):
        tax_def = self.tax_definition_ids.filtered(
            lambda d: d.tax_group_id == tax.tax_group_id
        )

        tax_def_values = {
            "type_in_out": "out",
            "tax_group_id": tax.tax_group_id.id,
            "is_taxed": True,
            "is_debit_credit": True,
            "custom_tax": True,
            "tax_id": tax.id,
            "cst_id": tax.cst_out_id.id,
            "company_id": self._origin.id,
        }

        if tax_def:
            tax_def.update(tax_def_values)
        else:
            self.tax_definition_ids |= self.tax_definition_ids.create(tax_def_values)

    @api.onchange("cnae_main_id")
    def _onchange_cnae_main_id(self):
        if self.cnae_main_id:
            simplified_tax = self.env["l10n_br_fiscal.simplified.tax"].search(
                [("cnae_ids", "=", self.cnae_main_id.id)], limit=1)

            if simplified_tax:
                self.simplifed_tax_id = simplified_tax.id

    @api.onchange("profit_calculation", "tax_framework")
    def _onchange_profit_calculation(self):

        # Get all Simples Nacional default taxes
        sn_piscofins_id = self.env.ref("l10n_br_fiscal.tax_pis_cofins_simples_nacional")

        sn_tax_icms_id = self.env.ref("l10n_br_fiscal.tax_icms_sn_com_credito")

        # If Tax Framework is Simples Nacional
        if self.tax_framework in TAX_FRAMEWORK_SIMPLES_ALL:
            # Set taxes
            self.piscofins_id = sn_piscofins_id
            self.tax_icms_id = sn_tax_icms_id

        # If Tax Framework is Regine Normal
        if self.tax_framework == TAX_FRAMEWORK_NORMAL:
            pis_cofins_refs = {
                "real": self.env.ref("l10n_br_fiscal.tax_pis_cofins_nao_columativo"),
                "presumed": self.env.ref("l10n_br_fiscal.tax_pis_cofins_columativo"),
                "arbitrary": self.env.ref("l10n_br_fiscal.tax_pis_cofins_columativo"),
            }

            self.piscofins_id = pis_cofins_refs.get(self.profit_calculation)
            self.tax_icms_id = False

        self._onchange_cnae_main_id()
        self._onchange_piscofins_id()
        self._onchange_ripi()
        self._onchange_tax_ipi_id()
        self._onchange_tax_icms_id()
        self._onchange_tax_issqn_id()
        self._onchange_tax_csll_id()
        self._onchange_tax_irpj_id()
        self._onchange_tax_inss_id()

        self._onchange_tax_issqn_wh_id()
        self._onchange_tax_pis_wh_id()
        self._onchange_tax_cofins_wh_id()
        self._onchange_tax_csll_wh_id()
        self._onchange_tax_irpj_wh_id()
        self._onchange_tax_inss_wh_id()

    @api.onchange("is_industry")
    def _onchange_is_industry(self):
        if self.is_industry and self.tax_framework == TAX_FRAMEWORK_SIMPLES:
            self.ripi = True
        else:
            self.ripi = False

    @api.onchange("ripi")
    def _onchange_ripi(self):
        if not self.ripi and self.tax_framework == TAX_FRAMEWORK_NORMAL:
            self.tax_ipi_id = self.env.ref("l10n_br_fiscal.tax_ipi_nt")
        elif self.tax_framework in TAX_FRAMEWORK_SIMPLES_ALL:
            self.tax_ipi_id = self.env.ref("l10n_br_fiscal.tax_ipi_simples_nacional")
            self.ripi = False
        else:
            self.tax_ipi_id = False

    @api.onchange("piscofins_id")
    def _onchange_piscofins_id(self):
        if self.piscofins_id:
            self._set_tax_definition(self.piscofins_id.tax_cofins_id)
            self._set_tax_definition(self.piscofins_id.tax_pis_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_PIS)
            self._del_tax_definition(TAX_DOMAIN_COFINS)

    @api.onchange("tax_pis_wh_id")
    def _onchange_tax_pis_wh_id(self):
        if self.tax_pis_wh_id:
            self._set_tax_definition(self.tax_pis_wh_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_PIS_WH)

    @api.onchange("tax_cofins_wh_id")
    def _onchange_tax_cofins_wh_id(self):
        if self.tax_cofins_wh_id:
            self._set_tax_definition(self.tax_cofins_wh_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_COFINS_WH)

    @api.onchange("tax_csll_id")
    def _onchange_tax_csll_id(self):
        if self.tax_csll_id:
            self._set_tax_definition(self.tax_csll_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_CSLL)

    @api.onchange("tax_csll_wh_id")
    def _onchange_tax_csll_wh_id(self):
        if self.tax_csll_wh_id:
            self._set_tax_definition(self.tax_csll_wh_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_CSLL_WH)

    @api.onchange("tax_ipi_id")
    def _onchange_tax_ipi_id(self):
        if self.tax_ipi_id:
            self._set_tax_definition(self.tax_ipi_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_IPI)

    @api.onchange("tax_icms_id")
    def _onchange_tax_icms_id(self):
        if self.tax_icms_id:
            self._set_tax_definition(self.tax_icms_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_ICMS)
            self._del_tax_definition(TAX_DOMAIN_ICMS_SN)

    @api.onchange("tax_issqn_id")
    def _onchange_tax_issqn_id(self):
        if self.tax_issqn_id:
            self._set_tax_definition(self.tax_issqn_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_ISSQN)

    @api.onchange("tax_issqn_wh_id")
    def _onchange_tax_issqn_wh_id(self):
        if self.tax_issqn_wh_id:
            self._set_tax_definition(self.tax_issqn_wh_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_ISSQN_WH)

    @api.onchange("tax_irpj_id")
    def _onchange_tax_irpj_id(self):
        if self.tax_irpj_id:
            self._set_tax_definition(self.tax_irpj_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_IRPJ)

    @api.onchange("tax_irpj_wh_id")
    def _onchange_tax_irpj_wh_id(self):
        if self.tax_irpj_wh_id:
            self._set_tax_definition(self.tax_irpj_wh_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_IRPJ_WH)

    @api.onchange("tax_inss_id")
    def _onchange_tax_inss_id(self):
        if self.tax_inss_id:
            self._set_tax_definition(self.tax_inss_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_INSS)

    @api.onchange("tax_inss_wh_id")
    def _onchange_tax_inss_wh_id(self):
        if self.tax_inss_wh_id:
            self._set_tax_definition(self.tax_inss_wh_id)
        else:
            self._del_tax_definition(TAX_DOMAIN_INSS_WH)
class AccountMoveReversal(models.TransientModel):
    """
    Account move reversal wizard, it cancel an account move by reversing it.
    """
    _name = 'account.move.reversal'
    _description = 'Account Move Reversal'

    @api.model
    def _get_default_move(self):
        if self._context.get('active_id'):
            move = self.env['account.move'].browse(self._context['active_id'])
            if move.state != 'posted' or move.type in ('out_refund', 'in_refund'):
                raise UserError(_('Only posted journal entries being not already a refund can be reversed.'))
            return move
        return self.env['account.move']

    @api.model
    def _get_default_reason(self):
        move = self._get_default_move()
        return move and move.invoice_payment_ref or False

    move_id = fields.Many2one('account.move', string='Journal Entry',
        default=_get_default_move,
        domain=[('state', '=', 'posted'), ('type', 'not in', ('out_refund', 'in_refund'))])
    date = fields.Date(string='Reversal date', default=fields.Date.context_today, required=True)
    reason = fields.Char(string='Reason', default=_get_default_reason)
    refund_method = fields.Selection(selection=[
            ('refund', 'Create a draft credit note (partial refunding)'),
            ('cancel', 'Cancel: create credit note and reconcile (full refunding)'),
            ('modify', 'Create credit note, reconcile and create a new draft invoice (cancel)')
        ], default='refund', string='Credit Method', required=True,
        help='Choose how you want to credit this invoice. You cannot "modify" nor "cancel" if the invoice is already reconciled.')
    journal_id = fields.Many2one('account.journal', string='Use Specific Journal', help='If empty, uses the journal of the journal entry to be reversed.')

    # related fields
    residual = fields.Monetary(related='move_id.amount_residual')
    currency_id = fields.Many2one(related='move_id.currency_id')
    move_type = fields.Selection(related='move_id.type')

    def reverse_moves(self):
        moves = self.move_id or self.env['account.move'].browse(self._context['active_ids'])

        # Create default values.
        default_values_list = []
        for move in moves:
            default_values_list.append({
                'ref': _('Reversal of: %s') % move.name,
                'invoice_payment_ref': self.reason,
                'date': self.date or move.date,
                'invoice_date': move.is_invoice(include_receipts=True) and (self.date or move.date) or False,
                'journal_id': self.journal_id and self.journal_id.id or move.journal_id.id,
            })

        # Handle reverse method.
        if self.refund_method == 'cancel' or (moves and moves[0].type == 'entry'):
            new_moves = moves._reverse_moves(default_values_list, cancel=True)
        elif self.refund_method == 'modify':
            new_moves = moves._reverse_moves(default_values_list, cancel=True)
            moves_vals_list = []
            for move in moves.with_context(include_business_fields=True):
                moves_vals_list.append(move.copy_data({
                    'invoice_payment_ref': move.name,
                    'date': self.date or move.date,
                })[0])
            new_moves = moves.create(moves_vals_list)
        elif self.refund_method == 'refund':
            new_moves = moves._reverse_moves(default_values_list)
        else:
            return

        # Create action.
        action = {
            'name': _('Reverse Moves'),
            'type': 'ir.actions.act_window',
            'res_model': 'account.move',
        }
        if len(new_moves) == 1:
            action.update({
                'view_mode': 'form',
                'res_id': new_moves.id,
            })
        else:
            action.update({
                'view_mode': 'tree,form',
                'domain': [('id', 'in', new_moves.ids)],
            })
        return action
Exemple #10
0
class ProjectProductEmployeeMap(models.Model):
    _name = 'project.sale.line.employee.map'
    _description = 'Project Sales line, employee mapping'

    project_id = fields.Many2one('project.project', "Project", required=True)
    employee_id = fields.Many2one('hr.employee', "Employee", required=True)
    sale_line_id = fields.Many2one('sale.order.line',
                                   "Sale Order Item",
                                   compute="_compute_sale_line_id",
                                   store=True,
                                   readonly=False,
                                   domain="""[
            ('is_service', '=', True),
            ('is_expense', '=', False),
            ('state', 'in', ['sale', 'done']),
            ('order_partner_id', '=?', partner_id),
            '|', ('company_id', '=', False), ('company_id', '=', company_id)]"""
                                   )
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 related='project_id.company_id')
    partner_id = fields.Many2one(related='project_id.partner_id')
    price_unit = fields.Float("Unit Price",
                              compute='_compute_price_unit',
                              store=True,
                              readonly=True)
    currency_id = fields.Many2one('res.currency',
                                  string="Currency",
                                  compute='_compute_currency_id',
                                  store=True,
                                  readonly=False)
    cost = fields.Monetary(
        currency_field='cost_currency_id',
        compute='_compute_cost',
        store=True,
        readonly=False,
        help=
        "This cost overrides the employee's default timesheet cost in employee's HR Settings"
    )
    cost_currency_id = fields.Many2one('res.currency',
                                       string="Cost Currency",
                                       related='employee_id.currency_id',
                                       readonly=True)
    is_cost_changed = fields.Boolean('Is Cost Manually Changed',
                                     compute='_compute_is_cost_changed',
                                     store=True)

    _sql_constraints = [
        ('uniqueness_employee', 'UNIQUE(project_id,employee_id)',
         'An employee cannot be selected more than once in the mapping. Please remove duplicate(s) and try again.'
         ),
    ]

    @api.depends('partner_id')
    def _compute_sale_line_id(self):
        self.filtered(
            lambda map_entry: map_entry.sale_line_id and map_entry.partner_id
            and map_entry.sale_line_id.order_partner_id.commercial_partner_id
            != map_entry.partner_id.commercial_partner_id).update(
                {'sale_line_id': False})

    @api.depends('sale_line_id.price_unit')
    def _compute_price_unit(self):
        for line in self:
            if line.sale_line_id:
                line.price_unit = line.sale_line_id.price_unit
            else:
                line.price_unit = 0

    @api.depends('sale_line_id.price_unit')
    def _compute_currency_id(self):
        for line in self:
            line.currency_id = line.sale_line_id.currency_id if line.sale_line_id else False

    @api.depends('employee_id.timesheet_cost')
    def _compute_cost(self):
        for map_entry in self:
            if not map_entry.is_cost_changed:
                map_entry.cost = map_entry.employee_id.timesheet_cost or 0.0

    @api.depends('cost')
    def _compute_is_cost_changed(self):
        for map_entry in self:
            map_entry.is_cost_changed = map_entry.employee_id and map_entry.cost != map_entry.employee_id.timesheet_cost

    @api.model
    def create(self, values):
        res = super(ProjectProductEmployeeMap, self).create(values)
        res._update_project_timesheet()
        return res

    def write(self, values):
        res = super(ProjectProductEmployeeMap, self).write(values)
        self._update_project_timesheet()
        return res

    def _update_project_timesheet(self):
        self.filtered(lambda l: l.sale_line_id
                      ).project_id._update_timesheets_sale_line_id()
Exemple #11
0
class account_payment(models.Model):
    _name = "account.payment"
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _description = "Payments"
    _order = "payment_date desc, name desc"

    name = fields.Char(readonly=True,
                       copy=False)  # The name is attributed upon post()
    payment_reference = fields.Char(
        copy=False,
        readonly=True,
        help=
        "Reference of the document used to issue this payment. Eg. check number, file name, etc."
    )
    move_name = fields.Char(
        string='Journal Entry Name',
        readonly=True,
        default=False,
        copy=False,
        help=
        "Technical field holding the number given to the journal entry, automatically set when the statement line is reconciled then stored to set the same number again if the line is cancelled, set to draft and re-processed again."
    )

    # 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'))],
        readonly=True,
        states={'draft': [('readonly', False)]})

    invoice_ids = fields.Many2many(
        'account.invoice',
        'account_invoice_payment_rel',
        'payment_id',
        'invoice_id',
        string="Invoices",
        copy=False,
        readonly=True,
        help=
        """Technical field containing the invoice for which the payment has been generated.
                                   This does not especially correspond to the invoices reconciled with the payment,
                                   as it can have been generated first, and reconciled later"""
    )
    reconciled_invoice_ids = fields.Many2many(
        'account.invoice',
        string='Reconciled Invoices',
        compute='_compute_reconciled_invoice_ids',
        help=
        "Invoices whose journal items have been reconciled with this payment's."
    )
    has_invoices = fields.Boolean(
        compute="_compute_reconciled_invoice_ids",
        help="Technical field used for usability purposes")

    move_line_ids = fields.One2many('account.move.line',
                                    'payment_id',
                                    readonly=True,
                                    copy=False,
                                    ondelete='restrict')
    move_reconciled = fields.Boolean(compute="_get_move_reconciled",
                                     readonly=True)

    state = fields.Selection([('draft', 'Draft'), ('posted', 'Posted'),
                              ('sent', 'Sent'), ('reconciled', 'Reconciled'),
                              ('cancelled', 'Cancelled')],
                             readonly=True,
                             default='draft',
                             copy=False,
                             string="Status")
    payment_type = fields.Selection([('outbound', 'Send Money'),
                                     ('inbound', 'Receive Money'),
                                     ('transfer', 'Internal Transfer')],
                                    string='Payment Type',
                                    required=True,
                                    readonly=True,
                                    states={'draft': [('readonly', False)]})
    payment_method_id = fields.Many2one('account.payment.method', string='Payment Method Type', required=True, readonly=True, states={'draft': [('readonly', False)]}, oldname="payment_method",
        help="Manual: Get paid by cash, check or any other method outside of Odoo.\n"\
        "Electronic: Get paid automatically through a payment acquirer by requesting a transaction on a card saved by the customer when buying or subscribing online (payment token).\n"\
        "Check: Pay bill by check and print it from Odoo.\n"\
        "Batch Deposit: Encase several customer checks at once by generating a batch deposit to submit to your bank. When encoding the bank statement in Odoo, you are suggested to reconcile the transaction with the batch deposit.To enable batch deposit, module account_batch_payment must be installed.\n"\
        "SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your bank. To enable sepa credit transfer, module account_sepa must be installed ")
    payment_method_code = fields.Char(
        related='payment_method_id.code',
        help=
        "Technical field used to adapt the interface to the payment type selected.",
        readonly=True)

    partner_type = fields.Selection([('customer', 'Customer'),
                                     ('supplier', 'Vendor')],
                                    tracking=True,
                                    readonly=True,
                                    states={'draft': [('readonly', False)]})
    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 tracking=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]})

    amount = fields.Monetary(string='Payment Amount',
                             required=True,
                             readonly=True,
                             states={'draft': [('readonly', False)]},
                             tracking=True)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
        default=lambda self: self.env.company_id.currency_id)
    payment_date = fields.Date(string='Payment Date',
                               default=fields.Date.context_today,
                               required=True,
                               readonly=True,
                               states={'draft': [('readonly', False)]},
                               copy=False,
                               tracking=True)
    communication = fields.Char(string='Memo',
                                readonly=True,
                                states={'draft': [('readonly', False)]})
    journal_id = fields.Many2one('account.journal',
                                 string='Payment Journal',
                                 required=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]},
                                 tracking=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'")

    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 Handling",
        copy=False)
    writeoff_account_id = fields.Many2one('account.account',
                                          string="Difference Account",
                                          domain=[('deprecated', '=', False)],
                                          copy=False)
    writeoff_label = fields.Char(
        string='Journal Item Label',
        help=
        'Change label of the counterpart that will hold the payment difference',
        default='Write-Off')
    partner_bank_account_id = fields.Many2one(
        'res.partner.bank',
        string="Recipient Bank Account",
        readonly=True,
        states={'draft': [('readonly', False)]})
    show_partner_bank_account = fields.Boolean(
        compute='_compute_show_partner_bank',
        help=
        'Technical field used to know whether the field `partner_bank_account_id` needs to be displayed or not in the payments form views'
    )
    require_partner_bank_account = fields.Boolean(
        compute='_compute_show_partner_bank',
        help=
        'Technical field used to know whether the field `partner_bank_account_id` needs to be required or not in the payments form views'
    )

    @api.model
    def default_get(self, fields):
        rec = super(account_payment, self).default_get(fields)
        active_ids = self._context.get('active_ids') or self._context.get(
            'active_id')
        active_model = self._context.get('active_model')

        # Check for selected invoices ids
        if not active_ids or active_model != 'account.invoice':
            return rec

        invoices = self.env['account.invoice'].browse(active_ids)

        # Check all invoices are open
        if any(invoice.state != 'open' for invoice in invoices):
            raise UserError(
                _("You can only register payments for open invoices"))
        # Check if, in batch payments, there are not negative invoices and positive invoices
        dtype = invoices[0].type
        for inv in invoices[1:]:
            if inv.type != dtype:
                if ((dtype == 'in_refund' and inv.type == 'in_invoice') or
                    (dtype == 'in_invoice' and inv.type == 'in_refund')):
                    raise UserError(
                        _("You cannot register payments for vendor bills and supplier refunds at the same time."
                          ))
                if ((dtype == 'out_refund' and inv.type == 'out_invoice') or
                    (dtype == 'out_invoice' and inv.type == 'out_refund')):
                    raise UserError(
                        _("You cannot register payments for customer invoices and credit notes at the same time."
                          ))

        amount = self._compute_payment_amount(invoices,
                                              invoices[0].currency_id)
        rec.update({
            'currency_id':
            invoices[0].currency_id.id,
            'amount':
            abs(amount),
            'payment_type':
            'inbound' if amount > 0 else 'outbound',
            'partner_id':
            invoices[0].commercial_partner_id.id,
            'partner_type':
            MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type],
            'communication':
            invoices[0].reference or invoices[0].number,
            'invoice_ids': [(6, 0, invoices.ids)],
        })
        return rec

    @api.one
    @api.constrains('amount')
    def _check_amount(self):
        if self.amount < 0:
            raise ValidationError(_('The payment amount cannot be negative.'))

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

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

    @api.depends('payment_method_code')
    def _compute_show_partner_bank(self):
        """ Computes if the destination bank account must be displayed in the payment form view. By default, it
        won't be displayed but some modules might change that, depending on the payment type."""
        for payment in self:
            payment.show_partner_bank_account = payment.payment_method_code in self._get_method_codes_using_bank_account(
            )
            payment.require_partner_bank_account = payment.payment_method_code in self._get_method_codes_needing_bank_account(
            )

    @api.multi
    @api.depends('payment_type', 'journal_id')
    def _compute_hide_payment_method(self):
        for payment in self:
            if not payment.journal_id or payment.journal_id.type not in [
                    'bank', 'cash'
            ]:
                payment.hide_payment_method = True
                continue
            journal_payment_methods = payment.payment_type == 'inbound'\
                and payment.journal_id.inbound_payment_method_ids\
                or payment.journal_id.outbound_payment_method_ids
            payment.hide_payment_method = len(
                journal_payment_methods
            ) == 1 and journal_payment_methods[0].code == 'manual'

    @api.depends('invoice_ids', 'amount', 'payment_date', 'currency_id',
                 'payment_type')
    def _compute_payment_difference(self):
        for pay in self.filtered(
                lambda p: p.invoice_ids and p.state == 'draft'):
            payment_amount = -pay.amount if pay.payment_type == 'outbound' else pay.amount
            pay.payment_difference = pay._compute_payment_amount(
            ) - payment_amount

    @api.onchange('journal_id')
    def _onchange_journal(self):
        if self.journal_id:
            # Set default payment method (we consider the first to be the default one)
            payment_methods = self.payment_type == 'inbound' and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids
            payment_methods_list = payment_methods.ids

            default_payment_method_id = self.env.context.get(
                'default_payment_method_id')
            if default_payment_method_id:
                # Ensure the domain will accept the provided default value
                payment_methods_list.append(default_payment_method_id)
            else:
                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'

            domain = {
                'payment_method_id': [('payment_type', '=', payment_type),
                                      ('id', 'in', payment_methods_list)]
            }

            if self.env.context.get('active_model') == 'account.invoice':
                active_ids = self._context.get('active_ids')
                invoices = self.env['account.invoice'].browse(active_ids)
                self.amount = abs(self._compute_payment_amount(invoices))

            return {'domain': domain}
        return {}

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        if self.invoice_ids and self.invoice_ids[0].partner_bank_id:
            self.partner_bank_account_id = self.invoice_ids[0].partner_bank_id
        elif self.partner_id != self.partner_bank_account_id.partner_id:
            # This condition ensures we use the default value provided into
            # context for partner_bank_account_id properly when provided with a
            # default partner_id. Without it, the onchange recomputes the bank account
            # uselessly and might assign a different value to it.
            if self.partner_id and len(self.partner_id.bank_ids) > 0:
                self.partner_bank_account_id = self.partner_id.bank_ids[0]
            elif self.partner_id and len(
                    self.partner_id.commercial_partner_id.bank_ids) > 0:
                self.partner_bank_account_id = self.partner_id.commercial_partner_id.bank_ids[
                    0]
            else:
                self.partner_bank_account_id = False
        return {
            'domain': {
                'partner_bank_account_id': [('partner_id', 'in', [
                    self.partner_id.id,
                    self.partner_id.commercial_partner_id.id
                ])]
            }
        }

    @api.onchange('partner_type')
    def _onchange_partner_type(self):
        self.ensure_one()
        # 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 and not self.partner_type:
            # 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'
        elif self.payment_type not in ('inbound', 'outbound'):
            self.partner_type = False
        # Set payment method domain
        res = self._onchange_journal()
        if not res.get('domain', {}):
            res['domain'] = {}
        jrnl_filters = self._compute_journal_domain_and_types()
        journal_types = jrnl_filters['journal_types']
        journal_types.update(['bank', 'cash'])
        res['domain']['journal_id'] = jrnl_filters['domain'] + [
            ('type', 'in', list(journal_types))
        ]
        return res

    def _compute_journal_domain_and_types(self):
        journal_type = ['bank', 'cash']
        domain = []
        if self.invoice_ids:
            domain.append(
                ('company_id', '=', self.invoice_ids[0].company_id.id))
        if self.currency_id.is_zero(self.amount) and self.has_invoices:
            # In case of payment with 0 amount, allow to select a journal of type 'general' like
            # 'Miscellaneous Operations' and set this journal by default.
            journal_type = ['general']
            self.payment_difference_handling = 'reconcile'
        else:
            if self.payment_type == 'inbound':
                domain.append(('at_least_one_inbound', '=', True))
            else:
                domain.append(('at_least_one_outbound', '=', True))
        return {'domain': domain, 'journal_types': set(journal_type)}

    @api.onchange('amount', 'currency_id')
    def _onchange_amount(self):
        jrnl_filters = self._compute_journal_domain_and_types()
        journal_types = jrnl_filters['journal_types']
        domain_on_types = [('type', 'in', list(journal_types))]
        if self.invoice_ids:
            domain_on_types.append(
                ('company_id', '=', self.invoice_ids[0].company_id.id))
        if self.journal_id.type not in journal_types or (
                self.invoice_ids and
                self.journal_id.company_id != self.invoice_ids[0].company_id):
            self.journal_id = self.env['account.journal'].search(
                domain_on_types, limit=1)
        return {
            'domain': {
                'journal_id': jrnl_filters['domain'] + domain_on_types
            }
        }

    @api.onchange('currency_id')
    def _onchange_currency(self):
        self.amount = abs(self._compute_payment_amount())

        if self.journal_id:  # TODO: only return if currency differ?
            return

        # Set by default the first liquidity journal having this currency if exists.
        domain = [('type', 'in', ('bank', 'cash')),
                  ('currency_id', '=', self.currency_id.id)]
        if self.invoice_ids:
            domain.append(
                ('company_id', '=', self.invoice_ids[0].company_id.id))
        journal = self.env['account.journal'].search(domain, limit=1)
        if journal:
            return {'value': {'journal_id': journal.id}}

    @api.multi
    def _compute_payment_amount(self, invoices=None, currency=None):
        '''Compute the total amount for the payment wizard.

        :param invoices: If not specified, pick all the invoices.
        :param currency: If not specified, search a default currency on wizard/journal.
        :return: The total amount to pay the invoices.
        '''

        # Get the payment invoices
        if not invoices:
            invoices = self.invoice_ids

        # Get the payment currency
        if not currency:
            currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id

        # Avoid currency rounding issues by summing the amounts according to the company_currency_id before
        invoice_datas = invoices.read_group(
            [('id', 'in', invoices.ids)],
            ['currency_id', 'type', 'residual_signed'],
            ['currency_id', 'type'],
            lazy=False)
        total = 0.0
        for invoice_data in invoice_datas:
            amount_total = MAP_INVOICE_TYPE_PAYMENT_SIGN[
                invoice_data['type']] * invoice_data['residual_signed']
            payment_currency = self.env['res.currency'].browse(
                invoice_data['currency_id'][0])
            if payment_currency == currency:
                total += amount_total
            else:
                total += payment_currency._convert(
                    amount_total, currency, self.env.company_id,
                    self.payment_date or fields.Date.today())
        return total

    @api.multi
    def name_get(self):
        return [(payment.id, payment.name or _('Draft Payment'))
                for payment in self]

    @api.multi
    @api.depends('move_line_ids.reconciled')
    def _get_move_reconciled(self):
        for payment in self:
            rec = True
            for aml in payment.move_line_ids.filtered(
                    lambda x: x.account_id.reconcile):
                if not aml.reconciled:
                    rec = False
                    break
            payment.move_reconciled = rec

    def open_payment_matching_screen(self):
        # Open reconciliation view for customers/suppliers
        move_line_id = False
        for move_line in self.move_line_ids:
            if move_line.account_id.reconcile:
                move_line_id = move_line.id
                break
        if not self.partner_id:
            raise UserError(_("Payments without a customer can't be matched"))
        action_context = {
            'company_ids': [self.company_id.id],
            'partner_ids': [self.partner_id.commercial_partner_id.id]
        }
        if self.partner_type == 'customer':
            action_context.update({'mode': 'customers'})
        elif self.partner_type == 'supplier':
            action_context.update({'mode': 'suppliers'})
        if move_line_id:
            action_context.update({'move_line_id': move_line_id})
        return {
            'type': 'ir.actions.client',
            'tag': 'manual_reconciliation_view',
            'context': action_context,
        }

    @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(
                    _('There is no Transfer Account defined in the accounting settings. Please define one to be able to confirm this transfer.'
                      ))
            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
        elif self.partner_type == 'customer':
            default_account = self.env['ir.property'].get(
                'property_account_receivable_id', 'res.partner')
            self.destination_account_id = default_account.id
        elif self.partner_type == 'supplier':
            default_account = self.env['ir.property'].get(
                'property_account_payable_id', 'res.partner')
            self.destination_account_id = default_account.id

    @api.depends('move_line_ids.matched_debit_ids',
                 'move_line_ids.matched_credit_ids')
    def _compute_reconciled_invoice_ids(self):
        for record in self:
            record.reconciled_invoice_ids = (
                record.move_line_ids.mapped(
                    'matched_debit_ids.debit_move_id.invoice_id')
                | record.move_line_ids.mapped(
                    'matched_credit_ids.credit_move_id.invoice_id'))
            record.has_invoices = bool(record.reconciled_invoice_ids)

    @api.multi
    def action_register_payment(self):
        active_ids = self.env.context.get('active_ids')
        if not active_ids:
            return ''

        return {
            'name':
            _('Register Payment'),
            'res_model':
            len(active_ids) == 1 and 'account.payment'
            or 'account.payment.register',
            'view_type':
            'form',
            'view_mode':
            'form',
            'view_id':
            len(active_ids) != 1
            and self.env.ref('account.view_account_payment_form_multi').id
            or self.env.ref('account.view_account_payment_invoice_form').id,
            'context':
            self.env.context,
            'target':
            'new',
            'type':
            'ir.actions.act_window',
        }

    @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.ids)],
        }

    @api.multi
    def button_invoices(self):
        if self.partner_type == 'supplier':
            views = [
                (self.env.ref('account.invoice_supplier_tree').id, 'tree'),
                (self.env.ref('account.invoice_supplier_form').id, 'form')
            ]
        else:
            views = [(self.env.ref('account.invoice_tree').id, 'tree'),
                     (self.env.ref('account.invoice_form').id, 'form')]
        return {
            'name': _('Paid Invoices'),
            'view_type': 'form',
            'view_mode': 'tree,form',
            'res_model': 'account.invoice',
            'view_id': False,
            'views': views,
            'type': 'ir.actions.act_window',
            'domain':
            [('id', 'in', [x.id for x in self.reconciled_invoice_ids])],
        }

    @api.multi
    def unreconcile(self):
        """ Set back the payments in 'posted' or 'sent' state, without deleting the journal entries.
            Called when cancelling a bank statement line linked to a pre-registered payment.
        """
        for payment in self:
            if payment.payment_reference:
                payment.write({'state': 'sent'})
            else:
                payment.write({'state': 'posted'})

    @api.multi
    def cancel(self):
        for rec in self:
            for move in rec.move_line_ids.mapped('move_id'):
                if rec.reconciled_invoice_ids:
                    move.line_ids.remove_move_reconcile()
                move.button_cancel()
                move.unlink()
            rec.state = 'cancelled'

    @api.multi
    def unlink(self):
        if any(bool(rec.move_line_ids) for rec in self):
            raise UserError(
                _("You cannot delete a payment that is already posted."))
        if any(rec.move_name for rec in self):
            raise UserError(
                _('It is not allowed to delete a payment that already created a journal entry since it would create a gap in the numbering. You should create the journal entry again and cancel it thanks to a regular revert.'
                  ))
        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 reconcilable account (see _compute_destination_account_id).
            If invoice_ids is not empty, there will be one reconcilable 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."))

            if any(inv.state != 'open' for inv in rec.invoice_ids):
                raise ValidationError(
                    _("The payment cannot be processed because the invoice is not open!"
                      ))

            # keep the name in case of a payment reset to draft
            if not rec.name:
                # Use the right sequence to set the name
                if rec.payment_type == 'transfer':
                    sequence_code = 'account.payment.transfer'
                else:
                    if rec.partner_type == 'customer':
                        if rec.payment_type == 'inbound':
                            sequence_code = 'account.payment.customer.invoice'
                        if rec.payment_type == 'outbound':
                            sequence_code = 'account.payment.customer.refund'
                    if rec.partner_type == 'supplier':
                        if rec.payment_type == 'inbound':
                            sequence_code = 'account.payment.supplier.refund'
                        if rec.payment_type == 'outbound':
                            sequence_code = 'account.payment.supplier.invoice'
                rec.name = self.env['ir.sequence'].with_context(
                    ir_sequence_date=rec.payment_date).next_by_code(
                        sequence_code)
                if not rec.name and rec.payment_type != 'transfer':
                    raise UserError(
                        _("You have to define a sequence for %s in your company."
                          ) % (sequence_code, ))

            # 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.write({'state': 'posted', 'move_name': move.name})
        return True

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

    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, currency_id = 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': currency_id})
        counterpart_aml = aml_obj.create(counterpart_aml_dict)

        #Reconcile with the invoices
        if self.payment_difference_handling == 'reconcile' and self.payment_difference:
            writeoff_line = self._get_shared_move_line_vals(
                0, 0, 0, move.id, False)
            debit_wo, credit_wo, amount_currency_wo, currency_id = aml_obj.with_context(
                date=self.payment_date)._compute_amount_fields(
                    self.payment_difference, self.currency_id,
                    self.company_id.currency_id)
            writeoff_line['name'] = self.writeoff_label
            writeoff_line['account_id'] = self.writeoff_account_id.id
            writeoff_line['debit'] = debit_wo
            writeoff_line['credit'] = credit_wo
            writeoff_line['amount_currency'] = amount_currency_wo
            writeoff_line['currency_id'] = currency_id
            writeoff_line = aml_obj.create(writeoff_line)
            if counterpart_aml['debit'] or (writeoff_line['credit']
                                            and not counterpart_aml['credit']):
                counterpart_aml['debit'] += credit_wo - debit_wo
            if counterpart_aml['credit'] or (writeoff_line['debit']
                                             and not counterpart_aml['debit']):
                counterpart_aml['credit'] += debit_wo - credit_wo
            counterpart_aml['amount_currency'] -= amount_currency_wo

        #Write counterpart lines
        if not self.currency_id.is_zero(self.amount):
            if not self.currency_id != self.company_id.currency_id:
                amount_currency = 0
            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)

        #validate the payment
        if not self.journal_id.post_at_bank_rec:
            move.post()

        #reconcile the invoice receivable/payable line(s) with the payment
        if self.invoice_ids:
            self.invoice_ids.register_payment(counterpart_aml)

        return move

    def _create_transfer_entry(self, amount):
        """ Create the journal entry corresponding to the 'incoming money' part of an internal transfer, return the reconcilable move line
        """
        aml_obj = self.env['account.move.line'].with_context(
            check_move_validity=False)
        debit, credit, amount_currency, dummy = 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._convert(
            amount, self.destination_journal_id.currency_id, self.company_id,
            self.payment_date or fields.Date.today()) 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,
            '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,
            '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)
        if not self.destination_journal_id.post_at_bank_rec:
            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
        move_vals = {
            'date': self.payment_date,
            'ref': self.communication or '',
            'company_id': self.company_id.id,
            'journal_id': journal.id,
        }
        if self.move_name:
            move_vals['name'] = self.move_name
        return move_vals

    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,
            'payment_id':
            self.id,
            'journal_id':
            self.journal_id.id,
        }

    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 Credit Note")
            elif self.partner_type == 'supplier':
                if self.payment_type == 'inbound':
                    name += _("Vendor Credit Note")
                elif self.payment_type == 'outbound':
                    name += _("Vendor Payment")
            if invoice:
                name += ': '
                for inv in invoice:
                    if inv.move_id:
                        name += inv.number + ', '
                name = name[:len(name) - 2]
        return {
            'name':
            name,
            'account_id':
            self.destination_account_id.id,
            'currency_id':
            self.currency_id != self.company_id.currency_id
            and self.currency_id.id or False,
        }

    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,
            '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._convert(
                amount, self.journal_id.currency_id, self.company_id,
                self.payment_date or fields.Date.today())
            debit, credit, amount_currency, dummy = 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

    def _get_invoice_payment_amount(self, inv):
        """
        Computes the amount covered by the current payment in the given invoice.

        :param inv: an invoice object
        :returns: the amount covered by the payment in the invoice
        """
        self.ensure_one()
        return sum([
            data['amount'] for data in inv._get_payments_vals()
            if data['account_payment_id'] == self.id
        ])
Exemple #12
0
class AccountPayment(models.Model):
    _name = "account.payment"
    _inherits = {'account.move': 'move_id'}
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _description = "Payments"
    _order = "date desc, name desc"
    _check_company_auto = True

    def _get_default_journal(self):
        ''' Retrieve the default journal for the account.payment.
        /!\ This method will not override the method in 'account.move' because the ORM
        doesn't allow overriding methods using _inherits. Then, this method will be called
        manually in 'create' and 'new'.
        :return: An account.journal record.
        '''
        return self.env['account.move']._search_default_journal(
            ('bank', 'cash'))

    # == Business fields ==
    move_id = fields.Many2one(comodel_name='account.move',
                              string='Journal Entry',
                              required=True,
                              readonly=True,
                              ondelete='cascade',
                              check_company=True)

    is_reconciled = fields.Boolean(
        string="Is Reconciled",
        store=True,
        compute='_compute_reconciliation_status',
        help="Technical field indicating if the payment is already reconciled."
    )
    is_matched = fields.Boolean(
        string="Is Matched With a Bank Statement",
        store=True,
        compute='_compute_reconciliation_status',
        help=
        "Technical field indicating if the payment has been matched with a statement line."
    )
    partner_bank_id = fields.Many2one(
        'res.partner.bank',
        string="Recipient Bank Account",
        readonly=False,
        store=True,
        compute='_compute_partner_bank_id',
        domain="[('partner_id', '=', partner_id)]",
        check_company=True)
    is_internal_transfer = fields.Boolean(
        string="Is Internal Transfer",
        readonly=False,
        store=True,
        compute="_compute_is_internal_transfer")
    qr_code = fields.Char(
        string="QR Code",
        compute="_compute_qr_code",
        help=
        "QR-code report URL to use to generate the QR-code to scan with a banking app to perform this payment."
    )

    # == Payment methods fields ==
    payment_method_id = fields.Many2one('account.payment.method', string='Payment Method',
        readonly=False, store=True,
        compute='_compute_payment_method_id',
        domain="[('id', 'in', available_payment_method_ids)]",
        help="Manual: Get paid by cash, check or any other method outside of Odoo.\n"\
        "Electronic: Get paid automatically through a payment acquirer by requesting a transaction on a card saved by the customer when buying or subscribing online (payment token).\n"\
        "Check: Pay bill by check and print it from Odoo.\n"\
        "Batch Deposit: Encase several customer checks at once by generating a batch deposit to submit to your bank. When encoding the bank statement in Odoo, you are suggested to reconcile the transaction with the batch deposit.To enable batch deposit, module account_batch_payment must be installed.\n"\
        "SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your bank. To enable sepa credit transfer, module account_sepa must be installed ")
    available_payment_method_ids = fields.Many2many(
        'account.payment.method', compute='_compute_payment_method_fields')
    hide_payment_method = fields.Boolean(
        compute='_compute_payment_method_fields',
        help=
        "Technical field used to hide the payment method if the selected journal has only one available which is 'manual'"
    )

    # == Synchronized fields with the account.move.lines ==
    amount = fields.Monetary(currency_field='currency_id')
    payment_type = fields.Selection([
        ('outbound', 'Send Money'),
        ('inbound', 'Receive Money'),
    ],
                                    string='Payment Type',
                                    default='inbound',
                                    required=True)
    partner_type = fields.Selection([
        ('customer', 'Customer'),
        ('supplier', 'Vendor'),
    ],
                                    default='customer',
                                    tracking=True,
                                    required=True)
    payment_reference = fields.Char(
        string="Payment Reference",
        copy=False,
        help=
        "Reference of the document used to issue this payment. Eg. check number, file name, etc."
    )
    currency_id = fields.Many2one('res.currency',
                                  string='Currency',
                                  store=True,
                                  readonly=False,
                                  compute='_compute_currency_id',
                                  help="The payment's currency.")
    partner_id = fields.Many2one(
        comodel_name='res.partner',
        string="Customer/Vendor",
        store=True,
        readonly=False,
        ondelete='restrict',
        compute='_compute_partner_id',
        domain="['|', ('parent_id','=', False), ('is_company','=', True)]",
        check_company=True)
    destination_account_id = fields.Many2one(
        comodel_name='account.account',
        string='Destination Account',
        store=True,
        readonly=False,
        compute='_compute_destination_account_id',
        domain=
        "[('user_type_id.type', 'in', ('receivable', 'payable')), ('company_id', '=', company_id)]",
        check_company=True,
        help="The payment's currency.")

    # == Stat buttons ==
    reconciled_invoice_ids = fields.Many2many(
        'account.move',
        string="Reconciled Invoices",
        compute='_compute_stat_buttons_from_reconciliation',
        help=
        "Invoices whose journal items have been reconciled with these payments."
    )
    reconciled_invoices_count = fields.Integer(
        string="# Reconciled Invoices",
        compute="_compute_stat_buttons_from_reconciliation")
    reconciled_bill_ids = fields.Many2many(
        'account.move',
        string="Reconciled Bills",
        compute='_compute_stat_buttons_from_reconciliation',
        help=
        "Invoices whose journal items have been reconciled with these payments."
    )
    reconciled_bills_count = fields.Integer(
        string="# Reconciled Bills",
        compute="_compute_stat_buttons_from_reconciliation")
    reconciled_statement_ids = fields.Many2many(
        'account.move',
        string="Reconciled Statements",
        compute='_compute_stat_buttons_from_reconciliation',
        help="Statements matched to this payment")
    reconciled_statements_count = fields.Integer(
        string="# Reconciled Statements",
        compute="_compute_stat_buttons_from_reconciliation")

    # == Display purpose fields ==
    payment_method_code = fields.Char(
        related='payment_method_id.code',
        help=
        "Technical field used to adapt the interface to the payment type selected."
    )
    show_partner_bank_account = fields.Boolean(
        compute='_compute_show_require_partner_bank',
        help=
        "Technical field used to know whether the field `partner_bank_id` needs to be displayed or not in the payments form views"
    )
    require_partner_bank_account = fields.Boolean(
        compute='_compute_show_require_partner_bank',
        help=
        "Technical field used to know whether the field `partner_bank_id` needs to be required or not in the payments form views"
    )
    country_code = fields.Char(related='company_id.country_id.code')

    _sql_constraints = [
        (
            'check_amount_not_negative',
            'CHECK(amount >= 0.0)',
            "The payment amount cannot be negative.",
        ),
    ]

    # -------------------------------------------------------------------------
    # HELPERS
    # -------------------------------------------------------------------------

    def _seek_for_lines(self):
        ''' Helper used to dispatch the journal items between:
        - The lines using the temporary liquidity account.
        - The lines using the counterpart account.
        - The lines being the write-off lines.
        :return: (liquidity_lines, counterpart_lines, writeoff_lines)
        '''
        self.ensure_one()

        liquidity_lines = self.env['account.move.line']
        counterpart_lines = self.env['account.move.line']
        writeoff_lines = self.env['account.move.line']

        for line in self.move_id.line_ids:
            if line.account_id in (self.journal_id.payment_debit_account_id,
                                   self.journal_id.payment_credit_account_id):
                liquidity_lines += line
            elif line.account_id.internal_type in (
                    'receivable', 'payable'
            ) or line.partner_id == line.company_id.partner_id:
                counterpart_lines += line
            else:
                writeoff_lines += line

        return liquidity_lines, counterpart_lines, writeoff_lines

    def _prepare_move_line_default_vals(self, write_off_line_vals=None):
        ''' Prepare the dictionary to create the default account.move.lines for the current payment.
        :param write_off_line_vals: Optional dictionary to create a write-off account.move.line easily containing:
            * amount:       The amount to be added to the counterpart amount.
            * name:         The label to set on the line.
            * account_id:   The account on which create the write-off.
        :return: A list of python dictionary to be passed to the account.move.line's 'create' method.
        '''
        self.ensure_one()
        write_off_line_vals = write_off_line_vals or {}

        if not self.journal_id.payment_debit_account_id or not self.journal_id.payment_credit_account_id:
            raise UserError(
                _(
                    "You can't create a new payment without an outstanding payments/receipts account set on the %s journal.",
                    self.journal_id.display_name))

        # Compute amounts.
        write_off_amount = write_off_line_vals.get('amount', 0.0)

        if self.payment_type == 'inbound':
            # Receive money.
            counterpart_amount = -self.amount
            write_off_amount *= -1
        elif self.payment_type == 'outbound':
            # Send money.
            counterpart_amount = self.amount
        else:
            counterpart_amount = 0.0
            write_off_amount = 0.0

        balance = self.currency_id._convert(counterpart_amount,
                                            self.company_id.currency_id,
                                            self.company_id, self.date)
        counterpart_amount_currency = counterpart_amount
        write_off_balance = self.currency_id._convert(
            write_off_amount, self.company_id.currency_id, self.company_id,
            self.date)
        write_off_amount_currency = write_off_amount
        currency_id = self.currency_id.id

        if self.is_internal_transfer:
            if self.payment_type == 'inbound':
                liquidity_line_name = _('Transfer to %s', self.journal_id.name)
            else:  # payment.payment_type == 'outbound':
                liquidity_line_name = _('Transfer from %s',
                                        self.journal_id.name)
        else:
            liquidity_line_name = self.payment_reference

        # Compute a default label to set on the journal items.

        payment_display_name = {
            'outbound-customer': _("Customer Reimbursement"),
            'inbound-customer': _("Customer Payment"),
            'outbound-supplier': _("Vendor Payment"),
            'inbound-supplier': _("Vendor Reimbursement"),
        }

        default_line_name = self.env[
            'account.move.line']._get_default_line_name(
                payment_display_name['%s-%s' %
                                     (self.payment_type, self.partner_type)],
                self.amount,
                self.currency_id,
                self.date,
                partner=self.partner_id,
            )

        line_vals_list = [
            # Liquidity line.
            {
                'name':
                liquidity_line_name or default_line_name,
                'date_maturity':
                self.date,
                'amount_currency':
                -counterpart_amount_currency,
                'currency_id':
                currency_id,
                'debit':
                balance < 0.0 and -balance or 0.0,
                'credit':
                balance > 0.0 and balance or 0.0,
                'partner_id':
                self.partner_id.id,
                'account_id':
                self.journal_id.payment_debit_account_id.id if balance < 0.0
                else self.journal_id.payment_credit_account_id.id,
            },
            # Receivable / Payable.
            {
                'name':
                self.payment_reference or default_line_name,
                'date_maturity':
                self.date,
                'amount_currency':
                counterpart_amount_currency +
                write_off_amount_currency if currency_id else 0.0,
                'currency_id':
                currency_id,
                'debit':
                balance + write_off_balance > 0.0
                and balance + write_off_balance or 0.0,
                'credit':
                balance + write_off_balance < 0.0
                and -balance - write_off_balance or 0.0,
                'partner_id':
                self.partner_id.id,
                'account_id':
                self.destination_account_id.id,
            },
        ]
        if write_off_balance:
            # Write-off line.
            line_vals_list.append({
                'name':
                write_off_line_vals.get('name') or default_line_name,
                'amount_currency':
                -write_off_amount_currency,
                'currency_id':
                currency_id,
                'debit':
                write_off_balance < 0.0 and -write_off_balance or 0.0,
                'credit':
                write_off_balance > 0.0 and write_off_balance or 0.0,
                'partner_id':
                self.partner_id.id,
                'account_id':
                write_off_line_vals.get('account_id'),
            })
        return line_vals_list

    # -------------------------------------------------------------------------
    # COMPUTE METHODS
    # -------------------------------------------------------------------------

    @api.depends('move_id.line_ids.amount_residual',
                 'move_id.line_ids.amount_residual_currency')
    def _compute_reconciliation_status(self):
        ''' Compute the field indicating if the payments are already reconciled with something.
        This field is used for display purpose (e.g. display the 'reconcile' button redirecting to the reconciliation
        widget).
        '''
        for pay in self:
            liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines(
            )

            if not pay.currency_id or not pay.id:
                pay.is_reconciled = False
                pay.is_matched = False
            elif pay.currency_id.is_zero(pay.amount):
                pay.is_reconciled = True
                pay.is_matched = True
            else:
                residual_field = 'amount_residual' if pay.currency_id == pay.company_id.currency_id else 'amount_residual_currency'
                if pay.journal_id.default_account_id and pay.journal_id.default_account_id in liquidity_lines.account_id:
                    # Allow user managing payments without any statement lines by using the bank account directly.
                    # In that case, the user manages transactions only using the register payment wizard.
                    pay.is_matched = True
                else:
                    pay.is_matched = pay.currency_id.is_zero(
                        sum(liquidity_lines.mapped(residual_field)))

                reconcile_lines = (counterpart_lines +
                                   writeoff_lines).filtered(
                                       lambda line: line.account_id.reconcile)
                pay.is_reconciled = pay.currency_id.is_zero(
                    sum(reconcile_lines.mapped(residual_field)))

    @api.model
    def _get_method_codes_using_bank_account(self):
        return ['manual']

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

    @api.depends('payment_method_code')
    def _compute_show_require_partner_bank(self):
        """ Computes if the destination bank account must be displayed in the payment form view. By default, it
        won't be displayed but some modules might change that, depending on the payment type."""
        for payment in self:
            payment.show_partner_bank_account = payment.payment_method_code in self._get_method_codes_using_bank_account(
            )
            payment.require_partner_bank_account = payment.state == 'draft' and payment.payment_method_code in self._get_method_codes_needing_bank_account(
            )

    @api.depends('partner_id')
    def _compute_partner_bank_id(self):
        ''' The default partner_bank_id will be the first available on the partner. '''
        for pay in self:
            available_partner_bank_accounts = pay.partner_id.bank_ids
            if available_partner_bank_accounts:
                pay.partner_bank_id = available_partner_bank_accounts[
                    0]._origin
            else:
                pay.partner_bank_id = False

    @api.depends('partner_id', 'destination_account_id', 'journal_id')
    def _compute_is_internal_transfer(self):
        for payment in self:
            is_partner_ok = payment.partner_id == payment.journal_id.company_id.partner_id
            is_account_ok = payment.destination_account_id and payment.destination_account_id == payment.journal_id.company_id.transfer_account_id
            payment.is_internal_transfer = is_partner_ok and is_account_ok

    @api.depends('payment_type', 'journal_id')
    def _compute_payment_method_id(self):
        ''' Compute the 'payment_method_id' field.
        This field is not computed in '_compute_payment_method_fields' because it's a stored editable one.
        '''
        for pay in self:
            if pay.payment_type == 'inbound':
                available_payment_methods = pay.journal_id.inbound_payment_method_ids
            else:
                available_payment_methods = pay.journal_id.outbound_payment_method_ids

            # Select the first available one by default.
            if available_payment_methods:
                pay.payment_method_id = available_payment_methods[0]._origin
            else:
                pay.payment_method_id = False

    @api.depends('payment_type', 'journal_id.inbound_payment_method_ids',
                 'journal_id.outbound_payment_method_ids')
    def _compute_payment_method_fields(self):
        for pay in self:
            if pay.payment_type == 'inbound':
                pay.available_payment_method_ids = pay.journal_id.inbound_payment_method_ids
            else:
                pay.available_payment_method_ids = pay.journal_id.outbound_payment_method_ids

            pay.hide_payment_method = len(
                pay.available_payment_method_ids
            ) == 1 and pay.available_payment_method_ids.code == 'manual'

    @api.depends('journal_id')
    def _compute_currency_id(self):
        for pay in self:
            pay.currency_id = pay.journal_id.currency_id or pay.journal_id.company_id.currency_id

    @api.depends('is_internal_transfer')
    def _compute_partner_id(self):
        for pay in self:
            if pay.is_internal_transfer:
                pay.partner_id = pay.journal_id.company_id.partner_id
            elif pay.partner_id == pay.journal_id.company_id.partner_id:
                pay.partner_id = False
            else:
                pay.partner_id = pay.partner_id

    @api.depends('journal_id', 'partner_id', 'partner_type',
                 'is_internal_transfer')
    def _compute_destination_account_id(self):
        self.destination_account_id = False
        for pay in self:
            if pay.is_internal_transfer:
                pay.destination_account_id = pay.journal_id.company_id.transfer_account_id
            elif pay.partner_type == 'customer':
                # Receive money from invoice or send money to refund it.
                if pay.partner_id:
                    pay.destination_account_id = pay.partner_id.with_company(
                        pay.company_id).property_account_receivable_id
                else:
                    pay.destination_account_id = self.env[
                        'account.account'].search([
                            ('company_id', '=', pay.company_id.id),
                            ('internal_type', '=', 'receivable'),
                        ],
                                                  limit=1)
            elif pay.partner_type == 'supplier':
                # Send money to pay a bill or receive money to refund it.
                if pay.partner_id:
                    pay.destination_account_id = pay.partner_id.with_company(
                        pay.company_id).property_account_payable_id
                else:
                    pay.destination_account_id = self.env[
                        'account.account'].search([
                            ('company_id', '=', pay.company_id.id),
                            ('internal_type', '=', 'payable'),
                        ],
                                                  limit=1)

    @api.depends('partner_bank_id', 'amount', 'ref', 'currency_id',
                 'journal_id', 'move_id.state', 'payment_method_id',
                 'payment_type')
    def _compute_qr_code(self):
        for pay in self:
            if pay.state in ('draft', 'posted') \
                and pay.partner_bank_id \
                and pay.payment_method_id.code == 'manual' \
                and pay.payment_type == 'outbound' \
                and pay.currency_id:

                if pay.partner_bank_id:
                    qr_code = pay.partner_bank_id.build_qr_code_url(
                        pay.amount, pay.ref, None, pay.currency_id,
                        pay.partner_id)
                else:
                    qr_code = None

                if qr_code:
                    pay.qr_code = '''
                        <br/>
                        <img class="border border-dark rounded" src="{qr_code}"/>
                        <br/>
                        <strong class="text-center">{txt}</strong>
                        '''.format(txt=_('Scan me with your banking app.'),
                                   qr_code=qr_code)
                    continue

            pay.qr_code = None

    @api.depends('move_id.line_ids.matched_debit_ids',
                 'move_id.line_ids.matched_credit_ids')
    def _compute_stat_buttons_from_reconciliation(self):
        ''' Retrieve the invoices reconciled to the payments through the reconciliation (account.partial.reconcile). '''
        stored_payments = self.filtered('id')
        if not stored_payments:
            self.reconciled_invoice_ids = False
            self.reconciled_invoices_count = 0
            self.reconciled_bill_ids = False
            self.reconciled_bills_count = 0
            self.reconciled_statement_ids = False
            self.reconciled_statements_count = 0
            return

        self.env['account.move'].flush()
        self.env['account.move.line'].flush()
        self.env['account.partial.reconcile'].flush()

        self._cr.execute(
            '''
            SELECT
                payment.id,
                ARRAY_AGG(DISTINCT invoice.id) AS invoice_ids,
                invoice.move_type
            FROM account_payment payment
            JOIN account_move move ON move.id = payment.move_id
            JOIN account_move_line line ON line.move_id = move.id
            JOIN account_partial_reconcile part ON
                part.debit_move_id = line.id
                OR
                part.credit_move_id = line.id
            JOIN account_move_line counterpart_line ON
                part.debit_move_id = counterpart_line.id
                OR
                part.credit_move_id = counterpart_line.id
            JOIN account_move invoice ON invoice.id = counterpart_line.move_id
            JOIN account_account account ON account.id = line.account_id
            WHERE account.internal_type IN ('receivable', 'payable')
                AND payment.id IN %(payment_ids)s
                AND line.id != counterpart_line.id
                AND invoice.move_type in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')
            GROUP BY payment.id, invoice.move_type
        ''', {'payment_ids': tuple(stored_payments.ids)})
        query_res = self._cr.dictfetchall()
        self.reconciled_invoice_ids = self.reconciled_invoices_count = False
        self.reconciled_bill_ids = self.reconciled_bills_count = False
        for res in query_res:
            pay = self.browse(res['id'])
            if res['move_type'] in self.env['account.move'].get_sale_types(
                    True):
                pay.reconciled_invoice_ids += self.env['account.move'].browse(
                    res.get('invoice_ids', []))
                pay.reconciled_invoices_count = len(res.get('invoice_ids', []))
            else:
                pay.reconciled_bill_ids += self.env['account.move'].browse(
                    res.get('invoice_ids', []))
                pay.reconciled_bills_count = len(res.get('invoice_ids', []))

        self._cr.execute(
            '''
            SELECT
                payment.id,
                ARRAY_AGG(DISTINCT counterpart_line.statement_id) AS statement_ids
            FROM account_payment payment
            JOIN account_move move ON move.id = payment.move_id
            JOIN account_journal journal ON journal.id = move.journal_id
            JOIN account_move_line line ON line.move_id = move.id
            JOIN account_account account ON account.id = line.account_id
            JOIN account_partial_reconcile part ON
                part.debit_move_id = line.id
                OR
                part.credit_move_id = line.id
            JOIN account_move_line counterpart_line ON
                part.debit_move_id = counterpart_line.id
                OR
                part.credit_move_id = counterpart_line.id
            WHERE (account.id = journal.payment_debit_account_id OR account.id = journal.payment_credit_account_id)
                AND payment.id IN %(payment_ids)s
                AND line.id != counterpart_line.id
                AND counterpart_line.statement_id IS NOT NULL
            GROUP BY payment.id
        ''', {'payment_ids': tuple(stored_payments.ids)})
        query_res = dict((payment_id, statement_ids)
                         for payment_id, statement_ids in self._cr.fetchall())

        for pay in self:
            statement_ids = query_res.get(pay.id, [])
            pay.reconciled_statement_ids = [(6, 0, statement_ids)]
            pay.reconciled_statements_count = len(statement_ids)

    # -------------------------------------------------------------------------
    # CONSTRAINT METHODS
    # -------------------------------------------------------------------------

    @api.constrains('payment_method_id')
    def _check_payment_method_id(self):
        ''' Ensure the 'payment_method_id' field is not null.
        Can't be done using the regular 'required=True' because the field is a computed editable stored one.
        '''
        for pay in self:
            if not pay.payment_method_id:
                raise ValidationError(
                    _("Please define a payment method on your payment."))

    # -------------------------------------------------------------------------
    # LOW-LEVEL METHODS
    # -------------------------------------------------------------------------

    @api.model_create_multi
    def create(self, vals_list):
        # OVERRIDE
        write_off_line_vals_list = []

        for vals in vals_list:

            # Hack to add a custom write-off line.
            write_off_line_vals_list.append(
                vals.pop('write_off_line_vals', None))

            # Force the move_type to avoid inconsistency with residual 'default_move_type' inside the context.
            vals['move_type'] = 'entry'

            # Force the computation of 'journal_id' since this field is set on account.move but must have the
            # bank/cash type.
            if 'journal_id' not in vals:
                vals['journal_id'] = self._get_default_journal().id

            # Since 'currency_id' is a computed editable field, it will be computed later.
            # Prevent the account.move to call the _get_default_currency method that could raise
            # the 'Please define an accounting miscellaneous journal in your company' error.
            if 'currency_id' not in vals:
                journal = self.env['account.journal'].browse(
                    vals['journal_id'])
                vals[
                    'currency_id'] = journal.currency_id.id or journal.company_id.currency_id.id

        payments = super().create(vals_list)

        for i, pay in enumerate(payments):
            write_off_line_vals = write_off_line_vals_list[i]

            # Write payment_id on the journal entry plus the fields being stored in both models but having the same
            # name, e.g. partner_bank_id. The ORM is currently not able to perform such synchronization and make things
            # more difficult by creating related fields on the fly to handle the _inherits.
            # Then, when partner_bank_id is in vals, the key is consumed by account.payment but is never written on
            # account.move.
            to_write = {'payment_id': pay.id}
            for k, v in vals_list[i].items():
                if k in self._fields and self._fields[
                        k].store and k in pay.move_id._fields and pay.move_id._fields[
                            k].store:
                    to_write[k] = v

            if 'line_ids' not in vals_list[i]:
                to_write['line_ids'] = [
                    (0, 0, line_vals)
                    for line_vals in pay._prepare_move_line_default_vals(
                        write_off_line_vals=write_off_line_vals)
                ]

            pay.move_id.write(to_write)

        return payments

    def write(self, vals):
        # OVERRIDE
        res = super().write(vals)
        self._synchronize_to_moves(set(vals.keys()))
        return res

    def unlink(self):
        # OVERRIDE to unlink the inherited account.move (move_id field) as well.
        moves = self.with_context(force_delete=True).move_id
        res = super().unlink()
        moves.unlink()
        return res

    @api.depends('move_id.name')
    def name_get(self):
        return [(payment.id, payment.move_id.name or _('Draft Payment'))
                for payment in self]

    # -------------------------------------------------------------------------
    # SYNCHRONIZATION account.payment <-> account.move
    # -------------------------------------------------------------------------

    def _synchronize_from_moves(self, changed_fields):
        ''' Update the account.payment regarding its related account.move.
        Also, check both models are still consistent.
        :param changed_fields: A set containing all modified fields on account.move.
        '''
        if self._context.get('skip_account_move_synchronization'):
            return

        for pay in self.with_context(skip_account_move_synchronization=True):
            move = pay.move_id
            move_vals_to_write = {}
            payment_vals_to_write = {}

            if 'journal_id' in changed_fields:
                if pay.journal_id.type not in ('bank', 'cash'):
                    raise UserError(
                        _("A payment must always belongs to a bank or cash journal."
                          ))

            if 'line_ids' in changed_fields:
                all_lines = move.line_ids
                liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines(
                )

                if len(liquidity_lines) != 1 or len(counterpart_lines) != 1:
                    raise UserError(
                        _("The journal entry %s reached an invalid state relative to its payment.\n"
                          "To be consistent, the journal entry must always contains:\n"
                          "- one journal item involving the outstanding payment/receipts account.\n"
                          "- one journal item involving a receivable/payable account.\n"
                          "- optional journal items, all sharing the same account.\n\n"
                          ) % move.display_name)

                if writeoff_lines and len(writeoff_lines.account_id) != 1:
                    raise UserError(
                        _("The journal entry %s reached an invalid state relative to its payment.\n"
                          "To be consistent, all the write-off journal items must share the same account."
                          ) % move.display_name)

                if any(line.currency_id != all_lines[0].currency_id
                       for line in all_lines):
                    raise UserError(
                        _("The journal entry %s reached an invalid state relative to its payment.\n"
                          "To be consistent, the journal items must share the same currency."
                          ) % move.display_name)

                if any(line.partner_id != all_lines[0].partner_id
                       for line in all_lines):
                    raise UserError(
                        _("The journal entry %s reached an invalid state relative to its payment.\n"
                          "To be consistent, the journal items must share the same partner."
                          ) % move.display_name)

                if counterpart_lines.account_id.user_type_id.type == 'receivable':
                    partner_type = 'customer'
                else:
                    partner_type = 'supplier'

                liquidity_amount = liquidity_lines.amount_currency

                move_vals_to_write.update({
                    'currency_id':
                    liquidity_lines.currency_id.id,
                    'partner_id':
                    liquidity_lines.partner_id.id,
                })
                payment_vals_to_write.update({
                    'amount':
                    abs(liquidity_amount),
                    'payment_type':
                    'inbound' if liquidity_amount > 0.0 else 'outbound',
                    'partner_type':
                    partner_type,
                    'currency_id':
                    liquidity_lines.currency_id.id,
                    'destination_account_id':
                    counterpart_lines.account_id.id,
                    'partner_id':
                    liquidity_lines.partner_id.id,
                })

            move.write(move._cleanup_write_orm_values(move,
                                                      move_vals_to_write))
            pay.write(
                move._cleanup_write_orm_values(pay, payment_vals_to_write))

    def _synchronize_to_moves(self, changed_fields):
        ''' Update the account.move regarding the modified account.payment.
        :param changed_fields: A list containing all modified fields on account.payment.
        '''
        if self._context.get('skip_account_move_synchronization'):
            return

        if not any(field_name in changed_fields for field_name in (
                'date',
                'amount',
                'payment_type',
                'partner_type',
                'payment_reference',
                'is_internal_transfer',
                'currency_id',
                'partner_id',
                'destination_account_id',
                'partner_bank_id',
        )):
            return

        for pay in self.with_context(skip_account_move_synchronization=True):
            liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines(
            )

            # Make sure to preserve the write-off amount.
            # This allows to create a new payment with custom 'line_ids'.

            if writeoff_lines:
                writeoff_amount = sum(writeoff_lines.mapped('amount_currency'))
                counterpart_amount = counterpart_lines['amount_currency']
                if writeoff_amount > 0.0 and counterpart_amount > 0.0:
                    sign = 1
                else:
                    sign = -1

                write_off_line_vals = {
                    'name': writeoff_lines[0].name,
                    'amount': writeoff_amount * sign,
                    'account_id': writeoff_lines[0].account_id.id,
                }
            else:
                write_off_line_vals = {}

            line_vals_list = pay._prepare_move_line_default_vals(
                write_off_line_vals=write_off_line_vals)

            line_ids_commands = [
                (1, liquidity_lines.id, line_vals_list[0]),
                (1, counterpart_lines.id, line_vals_list[1]),
            ]

            for line in writeoff_lines:
                line_ids_commands.append((2, line.id))

            if writeoff_lines:
                line_ids_commands.append((0, 0, line_vals_list[2]))

            # Update the existing journal items.
            # If dealing with multiple write-off lines, they are dropped and a new one is generated.

            pay.move_id.write({
                'partner_id': pay.partner_id.id,
                'currency_id': pay.currency_id.id,
                'partner_bank_id': pay.partner_bank_id.id,
                'line_ids': line_ids_commands,
            })

    # -------------------------------------------------------------------------
    # BUSINESS METHODS
    # -------------------------------------------------------------------------

    def mark_as_sent(self):
        self.write({'is_move_sent': True})

    def unmark_as_sent(self):
        self.write({'is_move_sent': False})

    def action_post(self):
        ''' draft -> posted '''
        self.move_id._post(soft=False)

    def action_cancel(self):
        ''' draft -> cancelled '''
        self.move_id.button_cancel()

    def action_draft(self):
        ''' posted -> draft '''
        self.move_id.button_draft()

    def button_open_invoices(self):
        ''' Redirect the user to the invoice(s) paid by this payment.
        :return:    An action on account.move.
        '''
        self.ensure_one()

        action = {
            'name': _("Paid Invoices"),
            'type': 'ir.actions.act_window',
            'res_model': 'account.move',
            'context': {
                'create': False
            },
        }
        if len(self.reconciled_invoice_ids) == 1:
            action.update({
                'view_mode': 'form',
                'res_id': self.reconciled_invoice_ids.id,
            })
        else:
            action.update({
                'view_mode':
                'list,form',
                'domain': [('id', 'in', self.reconciled_invoice_ids.ids)],
            })
        return action

    def button_open_bills(self):
        ''' Redirect the user to the bill(s) paid by this payment.
        :return:    An action on account.move.
        '''
        self.ensure_one()

        action = {
            'name': _("Paid Bills"),
            'type': 'ir.actions.act_window',
            'res_model': 'account.move',
            'context': {
                'create': False
            },
        }
        if len(self.reconciled_bill_ids) == 1:
            action.update({
                'view_mode': 'form',
                'res_id': self.reconciled_bill_ids.id,
            })
        else:
            action.update({
                'view_mode':
                'list,form',
                'domain': [('id', 'in', self.reconciled_bill_ids.ids)],
            })
        return action

    def button_open_statements(self):
        ''' Redirect the user to the statement line(s) reconciled to this payment.
        :return:    An action on account.move.
        '''
        self.ensure_one()

        action = {
            'name': _("Matched Statements"),
            'type': 'ir.actions.act_window',
            'res_model': 'account.bank.statement',
            'context': {
                'create': False
            },
        }
        if len(self.reconciled_statement_ids) == 1:
            action.update({
                'view_mode': 'form',
                'res_id': self.reconciled_statement_ids.id,
            })
        else:
            action.update({
                'view_mode':
                'list,form',
                'domain': [('id', 'in', self.reconciled_statement_ids.ids)],
            })
        return action
class resusers(models.Model):
    _inherit = 'res.users'

    x_nominal_otorisasi = fields.Monetary(string='Nominal Otorisasi',
                                          store=True,
                                          default=0.0)
class PurchaseOrder(models.Model):
    _inherit = "purchase.order"

    third_party_order = fields.Boolean(default=False,
                                       states=Purchase.READONLY_STATES)
    third_party_partner_id = fields.Many2one("res.partner",
                                             states=Purchase.READONLY_STATES)

    tp_amount_untaxed = fields.Monetary(
        string="Untaxed Amount",
        store=True,
        readonly=True,
        compute="_compute_amount_all_tp",
        track_visibility="always",
    )
    tp_amount_tax = fields.Monetary(
        string="Taxes",
        store=True,
        readonly=True,
        compute="_compute_amount_all_tp",
    )
    tp_amount_total = fields.Monetary(
        string="Total",
        store=True,
        readonly=True,
        compute="_compute_amount_all_tp",
    )

    @api.depends("order_line.third_party_price_total")
    def _compute_amount_all_tp(self):
        for order in self:
            tp_amount_untaxed = tp_amount_tax = 0.0
            for line in order.order_line:
                tp_amount_untaxed += line.third_party_price_subtotal
                tp_amount_tax += line.third_party_price_tax
            order.update({
                "tp_amount_untaxed":
                order.currency_id.round(tp_amount_untaxed),
                "tp_amount_tax":
                order.currency_id.round(tp_amount_tax),
                "tp_amount_total":
                tp_amount_untaxed + tp_amount_tax,
            })

    @api.multi
    def action_rfq_send(self):
        res = super().action_rfq_send()
        if self.env.context.get("third_party_send"):
            ctx = res.get("context")
            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_third_party",
                        "email_template_edi_purchase")[1]
                else:
                    template_id = ir_model_data.get_object_reference(
                        "purchase_third_party",
                        "email_template_edi_purchase_done",
                    )[1]
            except ValueError:
                template_id = False
            ctx.update({
                "default_use_template": bool(template_id),
                "default_template_id": template_id,
                "tpl_partners_only": False,
                "custom_layout": "purchase_third_party."
                "mail_template_data_notification_email_purchase_order",
                "not_display_company": True,
            })
            res.update({"context": ctx})
        return res
Exemple #15
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, account_type, operator, operand):
        if operator not in ('<', '=', '>', '>=', '<='):
            return []
        if type(operand) not in (float, int):
            return []
        sign = 1
        if account_type == 'payable':
            sign = -1
        res = self._cr.execute('''
            SELECT partner.id
            FROM res_partner partner
            LEFT JOIN account_move_line aml ON aml.partner_id = partner.id
            RIGHT JOIN account_account acc ON aml.account_id = acc.id
            WHERE acc.internal_type = %s
              AND NOT acc.deprecated
            GROUP BY partner.id
            HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, sign, operand))
        res = self._cr.fetchall()
        if not res:
            return [('id', '=', '0')]
        return [('id', 'in', map(itemgetter(0), res))]

    @api.model
    def _credit_search(self, operator, operand):
        return self._asset_difference_search('receivable', operator, operand)

    @api.model
    def _debit_search(self, operator, operand):
        return self._asset_difference_search('payable', operator, operand)

    @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
        all_partners_and_children = {}
        all_partner_ids = []
        for partner in self:
            # price_total is in the company currency
            all_partners_and_children[partner] = self.search([('id', 'child_of', partner.id)]).ids
            all_partner_ids += all_partners_and_children[partner]

        # searching account.invoice.report via the orm is comparatively expensive
        # (generates queries "id in []" forcing to build the full table).
        # In simple cases where all invoices are in the same currency than the user's company
        # access directly these elements

        # generate where clause to include multicompany rules
        where_query = account_invoice_report._where_calc([
            ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('company_id', '=', self.env.user.company_id.id),
            ('type', 'in', ('out_invoice', 'out_refund'))
        ])
        account_invoice_report._apply_ir_rules(where_query, 'read')
        from_clause, where_clause, where_clause_params = where_query.get_sql()

        # price_total is in the company currency
        query = """
                  SELECT SUM(price_total) as total, partner_id
                    FROM account_invoice_report account_invoice_report
                   WHERE %s
                   GROUP BY partner_id
                """ % where_clause
        self.env.cr.execute(query, where_clause_params)
        price_totals = self.env.cr.dictfetchall()
        for partner, child_ids in all_partners_and_children.items():
            partner.total_invoiced = sum(price['total'] for price in price_totals if price['partner_id'] in child_ids)

    @api.multi
    def _journal_item_count(self):
        for partner in self:
            partner.journal_item_count = self.env['account.move.line'].search_count([('partner_id', '=', partner.id)])
            partner.contracts_count = self.env['account.analytic.account'].search_count([('partner_id', '=', partner.id)])

    def get_followup_lines_domain(self, date, overdue_only=False, only_unblocked=False):
        domain = [('reconciled', '=', False), ('account_id.deprecated', '=', False), ('account_id.internal_type', '=', 'receivable'), '|', ('debit', '!=', 0), ('credit', '!=', 0), ('company_id', '=', self.env.user.company_id.id)]
        if only_unblocked:
            domain += [('blocked', '=', False)]
        if self.ids:
            if 'exclude_given_ids' in self._context:
                domain += [('partner_id', 'not in', self.ids)]
            else:
                domain += [('partner_id', 'in', self.ids)]
        #adding the overdue lines
        overdue_domain = ['|', '&', ('date_maturity', '!=', False), ('date_maturity', '<', date), '&', ('date_maturity', '=', False), ('date', '<', date)]
        if overdue_only:
            domain += overdue_domain
        return domain

    @api.multi
    def _compute_issued_total(self):
        """ Returns the issued total as will be displayed on partner view """
        today = fields.Date.context_today(self)
        for partner in self:
            domain = partner.get_followup_lines_domain(today, overdue_only=True)
            issued_total = 0
            for aml in self.env['account.move.line'].search(domain):
                issued_total += aml.amount_residual
            partner.issued_total = issued_total

    @api.one
    def _compute_has_unreconciled_entries(self):
        # Avoid useless work if has_unreconciled_entries is not relevant for this partner
        if not self.active or not self.is_company and self.parent_id:
            return
        self.env.cr.execute(
            """ SELECT 1 FROM(
                    SELECT
                        p.last_time_entries_checked AS last_time_entries_checked,
                        MAX(l.write_date) AS max_date
                    FROM
                        account_move_line l
                        RIGHT JOIN account_account a ON (a.id = l.account_id)
                        RIGHT JOIN res_partner p ON (l.partner_id = p.id)
                    WHERE
                        p.id = %s
                        AND EXISTS (
                            SELECT 1
                            FROM account_move_line l
                            WHERE l.account_id = a.id
                            AND l.partner_id = p.id
                            AND l.amount_residual > 0
                        )
                        AND EXISTS (
                            SELECT 1
                            FROM account_move_line l
                            WHERE l.account_id = a.id
                            AND l.partner_id = p.id
                            AND l.amount_residual < 0
                        )
                    GROUP BY p.last_time_entries_checked
                ) as s
                WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked)
            """, (self.id,))
        self.has_unreconciled_entries = self.env.cr.rowcount == 1

    @api.multi
    def mark_as_reconciled(self):
        self.env['account.partial.reconcile'].check_access_rights('write')
        return self.sudo().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,
        string="Currency", 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 Terms',
        help="This payment term will be used instead of the default one for sales orders and customer invoices", oldname="property_payment_term")
    property_supplier_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True,
         string='Vendor Payment Terms',
         help="This payment term will be used instead of the default one for purchase orders and vendor bills", 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)
    invoice_warn = fields.Selection(WARNING_MESSAGE, 'Invoice', help=WARNING_HELP, required=True, default="no-message")
    invoice_warn_msg = fields.Text('Message for Invoice')

    @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']

    def open_partner_history(self):
        '''
        This function returns an action that display invoices/refunds made for the given partners.
        '''
        action = self.env.ref('account.action_invoice_refund_out_tree')
        result = action.read()[0]
        result['domain'] = [('partner_id', 'in', self.ids)]
        return result
class InvoiceEletronic(models.Model):
    _inherit = 'invoice.eletronic'

    state = fields.Selection(selection_add=[('waiting',
                                             'Esperando processamento')])

    def _get_state_to_send(self):
        res = super(InvoiceEletronic, self)._get_state_to_send()
        return res + ('waiting', )

    @api.depends('valor_retencao_pis', 'valor_retencao_cofins',
                 'valor_retencao_irrf', 'valor_retencao_inss',
                 'valor_retencao_csll')
    def _compute_total_retencoes(self):
        for item in self:
            total = item.valor_retencao_pis + item.valor_retencao_cofins + \
                item.valor_retencao_irrf + item.valor_retencao_inss + \
                item.valor_retencao_csll
            item.retencoes_federais = total

    retencoes_federais = fields.Monetary(string="Retenções Federais",
                                         compute=_compute_total_retencoes)

    def _hook_validation(self):
        errors = super(InvoiceEletronic, self)._hook_validation()
        if self.model == '002':
            issqn_codigo = ''
            if not self.company_id.inscr_mun:
                errors.append(u'Inscrição municipal obrigatória')
            if not self.company_id.cnae_main_id.code:
                errors.append(u'CNAE Principal da empresa obrigatório')
            for eletr in self.eletronic_item_ids:
                prod = u"Produto: %s - %s" % (eletr.product_id.default_code,
                                              eletr.product_id.name)
                if eletr.tipo_produto == 'product':
                    errors.append(
                        u'Esse documento permite apenas serviços - %s' % prod)
                if eletr.tipo_produto == 'service':
                    if not eletr.issqn_codigo:
                        errors.append(u'%s - Código de Serviço' % prod)
                    if not issqn_codigo:
                        issqn_codigo = eletr.issqn_codigo
                    if issqn_codigo != eletr.issqn_codigo:
                        errors.append(u'%s - Apenas itens com o mesmo código \
                                      de serviço podem ser enviados' % prod)
                    if not eletr.codigo_tributacao_municipio:
                        errors.append(
                            u'%s - %s - Código de tributação do município \
                        obrigatório' % (eletr.product_id.name,
                                        eletr.product_id.service_type_id.name))

        return errors

    def _prepare_eletronic_invoice_values(self):
        res = super(InvoiceEletronic, self)._prepare_eletronic_invoice_values()
        if self.model != '002':
            return res

        tz = pytz.timezone(self.env.user.partner_id.tz) or pytz.utc
        dt_emissao = pytz.utc.localize(self.data_emissao).astimezone(tz)
        dt_emissao = dt_emissao.strftime('%Y-%m-%dT%H:%M:%S')

        partner = self.commercial_partner_id
        city_tomador = partner.city_id
        tomador = {
            'tipo_cpfcnpj':
            2 if partner.is_company else 1,
            'cnpj_cpf':
            re.sub('[^0-9]', '', partner.cnpj_cpf or ''),
            'razao_social':
            partner.legal_name or partner.name,
            'logradouro':
            partner.street or '',
            'numero':
            partner.number or '',
            'complemento':
            partner.street2 or '',
            'bairro':
            partner.district or 'Sem Bairro',
            'cidade':
            '%s%s' % (city_tomador.state_id.ibge_code, city_tomador.ibge_code),
            'uf':
            partner.state_id.code,
            'cep':
            re.sub('[^0-9]', '', partner.zip),
            'telefone':
            re.sub('[^0-9]', '', partner.phone or ''),
            'inscricao_municipal':
            re.sub('[^0-9]', '', partner.inscr_mun or ''),
            'email':
            self.partner_id.email or partner.email or '',
        }
        city_prestador = self.company_id.partner_id.city_id
        prestador = {
            'cnpj':
            re.sub('[^0-9]', '', self.company_id.partner_id.cnpj_cpf or ''),
            'inscricao_municipal':
            re.sub('[^0-9]', '', self.company_id.partner_id.inscr_mun or ''),
            'cidade':
            '%s%s' %
            (city_prestador.state_id.ibge_code, city_prestador.ibge_code),
            'cnae':
            re.sub('[^0-9]', '', self.company_id.cnae_main_id.code)
        }

        itens_servico = []
        descricao = ''
        codigo_servico = ''
        for item in self.eletronic_item_ids:
            descricao += item.name + '\n'
            itens_servico.append({
                'descricao':
                item.name,
                'quantidade':
                str("%.2f" % item.quantidade),
                'valor_unitario':
                str("%.2f" % item.preco_unitario)
            })
            codigo_servico = re.sub('[^0-9]', '', item.issqn_codigo)

        rps = {
            'numero': self.numero,
            'serie': self.serie.code or '',
            'tipo_rps': '1',
            'data_emissao': dt_emissao,
            'natureza_operacao': '1',  # Tributada no municipio
            'regime_tributacao': '2',  # Estimativa
            'optante_simples':  # 1 - Sim, 2 - Não
            '2' if self.company_id.fiscal_type == '3' else '1',
            'incentivador_cultural': '2',  # 2 - Não
            'status': '1',  # 1 - Normal
            'valor_servico': str("%.2f" % self.valor_servicos),
            'valor_deducao': '0',
            'valor_pis': str("%.2f" % self.valor_retencao_pis),
            'valor_cofins': str("%.2f" % self.valor_retencao_cofins),
            'valor_inss': str("%.2f" % self.valor_retencao_inss),
            'valor_ir': str("%.2f" % self.valor_retencao_irrf),
            'valor_csll': str("%.2f" % self.valor_retencao_csll),
            'iss_retido': '1' if self.valor_retencao_issqn > 0 else '2',
            'valor_iss':  str("%.2f" % self.valor_issqn),
            'valor_iss_retido': str("%.2f" % self.valor_retencao_issqn),
            'base_calculo': str("%.2f" % self.valor_bc_issqn),
            'aliquota_issqn': str("%.4f" % (
                self.eletronic_item_ids[0].issqn_aliquota / 100)),
            'valor_liquido_nfse': str("%.2f" % self.valor_final),
            'codigo_servico': int(codigo_servico),
            'codigo_tributacao_municipio':
            self.eletronic_item_ids[0].codigo_tributacao_municipio,
            # '01.07.00 / 00010700',
            'descricao': descricao,
            'codigo_municipio': prestador['cidade'],
            'itens_servico': itens_servico,
            'tomador': tomador,
            'prestador': prestador,
        }

        nfse_vals = {
            'numero_lote': self.id,
            'inscricao_municipal': prestador['inscricao_municipal'],
            'cnpj_prestador': prestador['cnpj'],
            'lista_rps': [rps],
        }

        res.update(nfse_vals)
        return res

    def action_post_validate(self):
        super(InvoiceEletronic, self).action_post_validate()
        if self.model not in ('002'):
            return

        cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file
        cert_pfx = base64.decodestring(cert)

        certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password)

        nfse_values = self._prepare_eletronic_invoice_values()
        xml_enviar = xml_recepcionar_lote_rps(certificado, nfse=nfse_values)

        self.xml_to_send = base64.encodestring(xml_enviar.encode('utf-8'))
        self.xml_to_send_name = 'nfse-enviar-%s.xml' % self.numero

    def _find_attachment_ids_email(self):
        atts = super(InvoiceEletronic, self)._find_attachment_ids_email()
        if self.model not in ('002'):
            return atts

        attachment_obj = self.env['ir.attachment']
        danfe_report = self.env['ir.actions.report'].search([
            ('report_name', '=',
             'br_nfse_ginfes.main_template_br_nfse_danfe_ginfes')
        ])
        report_service = danfe_report.xml_id
        report_name = safe_eval(danfe_report.print_report_name, {
            'object': self,
            'time': time
        })
        danfse, dummy = self.env.ref(report_service).render_qweb_pdf([self.id])
        filename = "%s.%s" % (report_name, "pdf")
        if danfse:
            danfe_id = attachment_obj.create(
                dict(
                    name=filename,
                    datas_fname=filename,
                    datas=base64.b64encode(danfse),
                    mimetype='application/pdf',
                    res_model='account.move',
                    res_id=self.invoice_id.id,
                ))
            atts.append(danfe_id.id)
        return atts

    def action_send_eletronic_invoice(self):
        super(InvoiceEletronic, self).action_send_eletronic_invoice()
        if self.model != '002' or self.state in ('done', 'cancel'):
            return
        self.state = 'error'

        cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file
        cert_pfx = base64.decodestring(cert)

        certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password)

        consulta_lote = None
        recebe_lote = None

        # Envia o lote apenas se não existir protocolo
        if not self.recibo_nfe:
            xml_to_send = base64.decodestring(self.xml_to_send)
            recebe_lote = recepcionar_lote_rps(certificado,
                                               xml=xml_to_send,
                                               ambiente=self.ambiente)

            retorno = recebe_lote['object']
            if "NumeroLote" in dir(retorno):
                self.recibo_nfe = retorno.Protocolo
                # Espera alguns segundos antes de consultar
                time.sleep(5)
            else:
                mensagem_retorno = retorno.ListaMensagemRetorno\
                    .MensagemRetorno
                self.codigo_retorno = mensagem_retorno.Codigo
                self.mensagem_retorno = mensagem_retorno.Mensagem
                self._create_attachment('nfse-ret', self,
                                        recebe_lote['received_xml'])
                return
        # Monta a consulta de situação do lote
        # 1 - Não Recebido
        # 2 - Não processado
        # 3 - Processado com erro
        # 4 - Processado com sucesso
        obj = {
            'cnpj_prestador': re.sub('[^0-9]', '', self.company_id.cnpj_cpf),
            'inscricao_municipal': re.sub('[^0-9]', '',
                                          self.company_id.inscr_mun),
            'protocolo': self.recibo_nfe,
        }
        consulta_situacao = consultar_situacao_lote(certificado,
                                                    consulta=obj,
                                                    ambiente=self.ambiente)
        ret_rec = consulta_situacao['object']

        if "Situacao" in dir(ret_rec):
            if ret_rec.Situacao in (3, 4):

                consulta_lote = consultar_lote_rps(certificado,
                                                   consulta=obj,
                                                   ambiente=self.ambiente)
                retLote = consulta_lote['object']

                if "ListaNfse" in dir(retLote):
                    self.state = 'done'
                    self.codigo_retorno = '100'
                    self.mensagem_retorno = 'NFSe emitida com sucesso'
                    self.verify_code = retLote.ListaNfse.CompNfse \
                        .Nfse.InfNfse.CodigoVerificacao
                    self.numero_nfse = \
                        retLote.ListaNfse.CompNfse.Nfse.InfNfse.Numero
                else:
                    mensagem_retorno = retLote.ListaMensagemRetorno \
                        .MensagemRetorno
                    self.codigo_retorno = mensagem_retorno.Codigo
                    self.mensagem_retorno = mensagem_retorno.Mensagem

            elif ret_rec.Situacao == 1:  # Reenviar caso não recebido
                self.codigo_retorno = ''
                self.mensagem_retorno = 'Aguardando envio'
                self.state = 'draft'
            else:
                self.state = 'waiting'
                self.codigo_retorno = '2'
                self.mensagem_retorno = 'Lote aguardando processamento'
        else:
            self.codigo_retorno = \
                ret_rec.ListaMensagemRetorno.MensagemRetorno.Codigo
            self.mensagem_retorno = \
                ret_rec.ListaMensagemRetorno.MensagemRetorno.Mensagem

        self.env['invoice.eletronic.event'].create({
            'code':
            self.codigo_retorno,
            'name':
            self.mensagem_retorno,
            'invoice_eletronic_id':
            self.id,
        })
        if recebe_lote:
            self._create_attachment('nfse-ret', self,
                                    recebe_lote['received_xml'])
        if consulta_lote:
            self._create_attachment('rec', self, consulta_lote['sent_xml'])
            self._create_attachment('rec-ret', self,
                                    consulta_lote['received_xml'])

    def action_cancel_document(self, context=None, justificativa=None):
        if self.model not in ('002'):
            return super(
                InvoiceEletronic,
                self).action_cancel_document(justificativa=justificativa)

        cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file
        cert_pfx = base64.decodestring(cert)

        certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password)

        company = self.company_id
        city_prestador = self.company_id.partner_id.city_id
        canc = {
            'cnpj_prestador':
            re.sub('[^0-9]', '', company.cnpj_cpf),
            'inscricao_municipal':
            re.sub('[^0-9]', '', company.inscr_mun),
            'cidade':
            '%s%s' %
            (city_prestador.state_id.ibge_code, city_prestador.ibge_code),
            'numero_nfse':
            self.numero_nfse,
            'codigo_cancelamento':
            '1',
            'senha':
            self.company_id.senha_ambiente_nfse
        }
        cancel = cancelar_nfse(certificado,
                               cancelamento=canc,
                               ambiente=self.ambiente)
        retorno = cancel['object'].Body.CancelarNfseResponse.CancelarNfseResult
        if "Cancelamento" in dir(retorno):
            self.state = 'cancel'
            self.codigo_retorno = '100'
            self.mensagem_retorno = u'Nota Fiscal de Serviço Cancelada'
        else:
            # E79 - Nota já está cancelada
            if retorno.ListaMensagemRetorno.MensagemRetorno.Codigo != 'E79':
                mensagem = "%s - %s" % (
                    retorno.ListaMensagemRetorno.MensagemRetorno.Codigo,
                    retorno.ListaMensagemRetorno.MensagemRetorno.Mensagem)
                raise UserError(mensagem)

            self.state = 'cancel'
            self.codigo_retorno = '100'
            self.mensagem_retorno = u'Nota Fiscal de Serviço Cancelada'

        self.env['invoice.eletronic.event'].create({
            'code':
            self.codigo_retorno,
            'name':
            self.mensagem_retorno,
            'invoice_eletronic_id':
            self.id,
        })
        self._create_attachment('canc', self, cancel['sent_xml'])
        self._create_attachment('canc-ret', self, cancel['received_xml'])
Exemple #17
0
class SpedOperacaoFiscal(models.Model):
    _name = b'sped.operacao'
    _description = 'Operações Fiscais'
    _order = 'emissao, modelo, nome'
    _rec_name = 'nome'

    empresa_id = fields.Many2one(comodel_name='sped.empresa',
                                 string='Empresa',
                                 ondelete='restrict')
    modelo = fields.Selection(selection=MODELO_FISCAL,
                              string='Modelo',
                              required=True,
                              index=True,
                              default=MODELO_FISCAL_NFE)
    emissao = fields.Selection(selection=TIPO_EMISSAO,
                               string='Tipo de emissão',
                               index=True,
                               default=TIPO_EMISSAO_PROPRIA)
    entrada_saida = fields.Selection(selection=ENTRADA_SAIDA,
                                     string='Entrada/saída',
                                     index=True,
                                     default=ENTRADA_SAIDA_SAIDA)
    nome = fields.Char(string='Nome', size=120, index=True)
    codigo = fields.Char(string='Código', size=60, index=True)
    serie = fields.Char(string='Série', size=3)
    regime_tributario = fields.Selection(selection=REGIME_TRIBUTARIO,
                                         string='Regime tributário',
                                         default=REGIME_TRIBUTARIO_SIMPLES)
    ind_forma_pagamento = fields.Selection(selection=IND_FORMA_PAGAMENTO,
                                           string='Tipo de pagamento',
                                           default=IND_FORMA_PAGAMENTO_A_VISTA)
    finalidade_nfe = fields.Selection(selection=FINALIDADE_NFE,
                                      string='Finalidade da NF-e',
                                      default=FINALIDADE_NFE_NORMAL)
    modalidade_frete = fields.Selection(
        selection=MODALIDADE_FRETE,
        string='Modalidade do frete',
        default=MODALIDADE_FRETE_DESTINATARIO_PROPRIO)
    natureza_operacao_id = fields.Many2one(
        comodel_name='sped.natureza.operacao',
        string='Natureza da operação',
        ondelete='restrict')
    infadfisco = fields.Text(
        string='Informações adicionais de interesse do fisco')
    infcomplementar = fields.Text(string='Informações complementares')
    #
    # Retenção de impostos
    #
    deduz_retencao = fields.Boolean(string='Deduz retenção do total da NF?',
                                    default=True)
    pis_cofins_retido = fields.Boolean(string='PIS-COFINS retidos?')
    al_pis_retido = fields.Float(string='Alíquota do PIS',
                                 default=0.65,
                                 digits=(5, 2))
    al_cofins_retido = fields.Float(string='Alíquota da COFINS',
                                    default=3,
                                    digits=(5, 2))
    csll_retido = fields.Boolean(string='CSLL retido?')
    al_csll = fields.Float(string='Alíquota da CSLL', default=1, digits=(5, 2))
    #
    # Para todos os valores de referência numa operação fiscal,
    # a moeda é SEMPRE o
    # Real BRL
    #
    currency_id = fields.Many2one(
        comodel_name='res.currency',
        string='Moeda',
        default=lambda self: self.env.ref('base.BRL').id)
    limite_retencao_pis_cofins_csll = fields.Monetary(
        string='Obedecer limite de faturamento para retenção de',
        default=LIMITE_RETENCAO_PIS_COFINS_CSLL)
    irrf_retido = fields.Boolean(string='IR retido?')
    irrf_retido_ignora_limite = fields.Boolean(
        string='IR retido ignora limite de R$ 10,00?', )
    al_irrf = fields.Float(
        string='Alíquota do IR',
        default=1,
        digits=(5, 2),
    )
    #
    # Notas de serviço
    #
    inss_retido = fields.Boolean(string='INSS retido?', )
    cnae_id = fields.Many2one(comodel_name='sped.cnae', string='CNAE')
    natureza_tributacao_nfse = fields.Selection(
        selection=NATUREZA_TRIBUTACAO_NFSE,
        string='Natureza da tributação',
    )
    servico_id = fields.Many2one(comodel_name='sped.servico', string='Serviço')
    cst_iss = fields.Selection(selection=ST_ISS, string='CST ISS')
    # 'forca_recalculo_st_compra': fields.boolean(
    # 'Força recálculo do ST na compra?'),
    # 'operacao_entrada_id': fields.many2one(
    # 'sped.operacao', 'Operação de entrada equivalente'),
    consumidor_final = fields.Selection(selection=TIPO_CONSUMIDOR_FINAL,
                                        string='Tipo do consumidor',
                                        default=TIPO_CONSUMIDOR_FINAL_NORMAL)
    presenca_comprador = fields.Selection(
        selection=INDICADOR_PRESENCA_COMPRADOR,
        string='Presença do comprador',
        default=INDICADOR_PRESENCA_COMPRADOR_NAO_SE_APLICA)
    preco_automatico = fields.Selection(
        selection=[
            ('V', 'Venda'),
            ('C', 'Custo'),
        ],
        string='Traz preço automático?',
    )
Exemple #18
0
class AccountAccount(models.Model):
    _name = "account.account"
    _inherit = ['mail.thread']
    _description = "Account"
    _order = "is_off_balance, code, company_id"
    _check_company_auto = True

    @api.constrains('internal_type', 'reconcile')
    def _check_reconcile(self):
        for account in self:
            if account.internal_type in ('receivable', 'payable') and account.reconcile == False:
                raise ValidationError(_('You cannot have a receivable/payable account that is not reconcilable. (account code: %s)', account.code))

    @api.constrains('user_type_id')
    def _check_user_type_id_unique_current_year_earning(self):
        data_unaffected_earnings = self.env.ref('account.data_unaffected_earnings')
        result = self._read_group(
            domain=[('user_type_id', '=', data_unaffected_earnings.id)],
            fields=['company_id', 'ids:array_agg(id)'],
            groupby=['company_id'],
        )
        for res in result:
            if res.get('company_id_count', 0) >= 2:
                account_unaffected_earnings = self.browse(res['ids'])
                raise ValidationError(_('You cannot have more than one account with "Current Year Earnings" as type. (accounts: %s)', [a.code for a in account_unaffected_earnings]))

    name = fields.Char(string="Account Name", required=True, index='trigram', tracking=True)
    currency_id = fields.Many2one('res.currency', string='Account Currency',
        help="Forces all moves for this account to have this account currency.", tracking=True)
    code = fields.Char(size=64, required=True, index=True, tracking=True)
    deprecated = fields.Boolean(default=False, tracking=True)
    used = fields.Boolean(compute='_compute_used', search='_search_used')
    user_type_id = fields.Many2one('account.account.type', string='Type', required=True, tracking=True,
        help="Account Type is used for information purpose, to generate country-specific legal reports, and set the rules to close a fiscal year and generate opening entries.")
    internal_type = fields.Selection(related='user_type_id.type', string="Internal Type", store=True, readonly=True)
    internal_group = fields.Selection(related='user_type_id.internal_group', string="Internal Group", store=True, readonly=True)
    #has_unreconciled_entries = fields.Boolean(compute='_compute_has_unreconciled_entries',
    #    help="The account has at least one unreconciled debit and credit since last time the invoices & payments matching was performed.")
    reconcile = fields.Boolean(string='Allow Reconciliation', default=False, tracking=True,
        help="Check this box if this account allows invoices & payments matching of journal items.")
    tax_ids = fields.Many2many('account.tax', 'account_account_tax_default_rel',
        'account_id', 'tax_id', string='Default Taxes',
        check_company=True,
        context={'append_type_to_tax_name': True})
    note = fields.Text('Internal Notes', tracking=True)
    company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True,
        default=lambda self: self.env.company)
    tag_ids = fields.Many2many('account.account.tag', 'account_account_account_tag', string='Tags', help="Optional tags you may want to assign for custom reporting")
    group_id = fields.Many2one('account.group', compute='_compute_account_group', store=True, readonly=True,
                               help="Account prefixes can determine account groups.")
    root_id = fields.Many2one('account.root', compute='_compute_account_root', store=True)
    allowed_journal_ids = fields.Many2many('account.journal', string="Allowed Journals", help="Define in which journals this account can be used. If empty, can be used in all journals.")

    opening_debit = fields.Monetary(string="Opening Debit", compute='_compute_opening_debit_credit', inverse='_set_opening_debit', help="Opening debit value for this account.")
    opening_credit = fields.Monetary(string="Opening Credit", compute='_compute_opening_debit_credit', inverse='_set_opening_credit', help="Opening credit value for this account.")
    opening_balance = fields.Monetary(string="Opening Balance", compute='_compute_opening_debit_credit', help="Opening balance value for this account.")

    is_off_balance = fields.Boolean(compute='_compute_is_off_balance', default=False, store=True, readonly=True)

    current_balance = fields.Float(compute='_compute_current_balance')
    related_taxes_amount = fields.Integer(compute='_compute_related_taxes_amount')

    _sql_constraints = [
        ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
    ]

    non_trade = fields.Boolean(default=False,
                               help="If set, this account will belong to Non Trade Receivable/Payable in reports and filters.\n"
                                    "If not, this account will belong to Trade Receivable/Payable in reports and filters.")

    @api.constrains('reconcile', 'internal_group', 'tax_ids')
    def _constrains_reconcile(self):
        for record in self:
            if record.internal_group == 'off_balance':
                if record.reconcile:
                    raise UserError(_('An Off-Balance account can not be reconcilable'))
                if record.tax_ids:
                    raise UserError(_('An Off-Balance account can not have taxes'))

    @api.constrains('allowed_journal_ids')
    def _constrains_allowed_journal_ids(self):
        self.env['account.move.line'].flush(['account_id', 'journal_id'])
        self.flush(['allowed_journal_ids'])
        self._cr.execute("""
            SELECT aml.id
            FROM account_move_line aml
            WHERE aml.account_id in %s
            AND EXISTS (SELECT 1 FROM account_account_account_journal_rel WHERE account_account_id = aml.account_id)
            AND NOT EXISTS (SELECT 1 FROM account_account_account_journal_rel WHERE account_account_id = aml.account_id AND account_journal_id = aml.journal_id)
        """, [tuple(self.ids)])
        ids = self._cr.fetchall()
        if ids:
            raise ValidationError(_('Some journal items already exist with this account but in other journals than the allowed ones.'))

    @api.constrains('currency_id')
    def _check_journal_consistency(self):
        ''' Ensure the currency set on the journal is the same as the currency set on the
        linked accounts.
        '''
        if not self:
            return

        self.env['account.account'].flush(['currency_id'])
        self.env['account.journal'].flush([
            'currency_id',
            'default_account_id',
            'suspense_account_id',
        ])
        self.env['account.payment.method'].flush(['payment_type'])
        self.env['account.payment.method.line'].flush(['payment_method_id', 'payment_account_id'])

        self._cr.execute('''
            SELECT
                account.id,
                journal.id
            FROM account_journal journal
            JOIN res_company company ON company.id = journal.company_id
            JOIN account_account account ON account.id = journal.default_account_id
            WHERE journal.currency_id IS NOT NULL
            AND journal.currency_id != company.currency_id
            AND account.currency_id != journal.currency_id
            AND account.id IN %(accounts)s

            UNION ALL

            SELECT
                account.id,
                journal.id
            FROM account_journal journal
            JOIN res_company company ON company.id = journal.company_id
            JOIN account_payment_method_line apml ON apml.journal_id = journal.id
            JOIN account_payment_method apm on apm.id = apml.payment_method_id
            JOIN account_account account ON account.id = COALESCE(apml.payment_account_id, company.account_journal_payment_debit_account_id)
            WHERE journal.currency_id IS NOT NULL
            AND journal.currency_id != company.currency_id
            AND account.currency_id != journal.currency_id
            AND apm.payment_type = 'inbound'
            AND account.id IN %(accounts)s

            UNION ALL

            SELECT
                account.id,
                journal.id
            FROM account_journal journal
            JOIN res_company company ON company.id = journal.company_id
            JOIN account_payment_method_line apml ON apml.journal_id = journal.id
            JOIN account_payment_method apm on apm.id = apml.payment_method_id
            JOIN account_account account ON account.id = COALESCE(apml.payment_account_id, company.account_journal_payment_credit_account_id)
            WHERE journal.currency_id IS NOT NULL
            AND journal.currency_id != company.currency_id
            AND account.currency_id != journal.currency_id
            AND apm.payment_type = 'outbound'
            AND account.id IN %(accounts)s
        ''', {
            'accounts': tuple(self.ids)
        })
        res = self._cr.fetchone()
        if res:
            account = self.env['account.account'].browse(res[0])
            journal = self.env['account.journal'].browse(res[1])
            raise ValidationError(_(
                "The foreign currency set on the journal '%(journal)s' and the account '%(account)s' must be the same.",
                journal=journal.display_name,
                account=account.display_name
            ))

    @api.constrains('company_id')
    def _check_company_consistency(self):
        if not self:
            return

        self.flush(['company_id'])
        self._cr.execute('''
            SELECT line.id
            FROM account_move_line line
            JOIN account_account account ON account.id = line.account_id
            WHERE line.account_id IN %s
            AND line.company_id != account.company_id
        ''', [tuple(self.ids)])
        if self._cr.fetchone():
            raise UserError(_("You can't change the company of your account since there are some journal items linked to it."))

    @api.constrains('user_type_id')
    def _check_user_type_id_sales_purchase_journal(self):
        if not self:
            return

        self.flush(['user_type_id'])
        self._cr.execute('''
            SELECT account.id
            FROM account_account account
            JOIN account_account_type acc_type ON account.user_type_id = acc_type.id
            JOIN account_journal journal ON journal.default_account_id = account.id
            WHERE account.id IN %s
            AND acc_type.type IN ('receivable', 'payable')
            AND journal.type IN ('sale', 'purchase')
            LIMIT 1;
        ''', [tuple(self.ids)])

        if self._cr.fetchone():
            raise ValidationError(_("The account is already in use in a 'sale' or 'purchase' journal. This means that the account's type couldn't be 'receivable' or 'payable'."))

    @api.constrains('reconcile')
    def _check_used_as_journal_default_debit_credit_account(self):
        accounts = self.filtered(lambda a: not a.reconcile)
        if not accounts:
            return

        self.flush(['reconcile'])
        self.env['account.payment.method.line'].flush(['journal_id', 'payment_account_id'])

        self._cr.execute('''
            SELECT journal.id
            FROM account_journal journal
            JOIN res_company company on journal.company_id = company.id
            LEFT JOIN account_payment_method_line apml ON journal.id = apml.journal_id
            WHERE (
                company.account_journal_payment_credit_account_id IN %(accounts)s
                AND company.account_journal_payment_credit_account_id != journal.default_account_id
                ) OR (
                company.account_journal_payment_debit_account_id in %(accounts)s
                AND company.account_journal_payment_debit_account_id != journal.default_account_id
                ) OR (
                apml.payment_account_id IN %(accounts)s
                AND apml.payment_account_id != journal.default_account_id
            )
        ''', {
            'accounts': tuple(accounts.ids),
        })

        rows = self._cr.fetchall()
        if rows:
            journals = self.env['account.journal'].browse([r[0] for r in rows])
            raise ValidationError(_(
                "This account is configured in %(journal_names)s journal(s) (ids %(journal_ids)s) as payment debit or credit account. This means that this account's type should be reconcilable.",
                journal_names=journals.mapped('display_name'),
                journal_ids=journals.ids
            ))

    @api.depends('code')
    def _compute_account_root(self):
        # this computes the first 2 digits of the account.
        # This field should have been a char, but the aim is to use it in a side panel view with hierarchy, and it's only supported by many2one fields so far.
        # So instead, we make it a many2one to a psql view with what we need as records.
        for record in self:
            record.root_id = (ord(record.code[0]) * 1000 + ord(record.code[1:2] or '\x00')) if record.code else False

    @api.depends('code')
    def _compute_account_group(self):
        if self.ids:
            self.env['account.group']._adapt_accounts_for_account_groups(self)
        else:
            self.group_id = False

    def _search_used(self, operator, value):
        if operator not in ['=', '!='] or not isinstance(value, bool):
            raise UserError(_('Operation not supported'))
        if operator != '=':
            value = not value
        self._cr.execute("""
            SELECT id FROM account_account account
            WHERE EXISTS (SELECT 1 FROM account_move_line aml WHERE aml.account_id = account.id LIMIT 1)
        """)
        return [('id', 'in' if value else 'not in', [r[0] for r in self._cr.fetchall()])]

    def _compute_used(self):
        ids = set(self._search_used('=', True)[0][2])
        for record in self:
            record.used = record.id in ids

    @api.model
    def _search_new_account_code(self, company, digits, prefix):
        for num in range(1, 10000):
            new_code = str(prefix.ljust(digits - 1, '0')) + str(num)
            rec = self.search([('code', '=', new_code), ('company_id', '=', company.id)], limit=1)
            if not rec:
                return new_code
        raise UserError(_('Cannot generate an unused account code.'))

    def _compute_current_balance(self):
        balances = {
            read['account_id'][0]: read['balance']
            for read in self.env['account.move.line']._read_group(
                domain=[('account_id', 'in', self.ids)],
                fields=['balance', 'account_id'],
                groupby=['account_id'],
            )
        }
        for record in self:
            record.current_balance = balances.get(record.id, 0)

    def _compute_related_taxes_amount(self):
        for record in self:
            record.related_taxes_amount = self.env['account.tax'].search_count([
                '|',
                ('invoice_repartition_line_ids.account_id', '=', record.id),
                ('refund_repartition_line_ids.account_id', '=', record.id),
            ])

    def _compute_opening_debit_credit(self):
        self.opening_debit = 0
        self.opening_credit = 0
        self.opening_balance = 0
        if not self.ids:
            return
        self.env.cr.execute("""
            SELECT line.account_id,
                   SUM(line.balance) AS balance,
                   SUM(line.debit) AS debit,
                   SUM(line.credit) AS credit
              FROM account_move_line line
              JOIN res_company comp ON comp.id = line.company_id
             WHERE line.move_id = comp.account_opening_move_id
               AND line.account_id IN %s
             GROUP BY line.account_id
        """, [tuple(self.ids)])
        result = {r['account_id']: r for r in self.env.cr.dictfetchall()}
        for record in self:
            res = result.get(record.id) or {'debit': 0, 'credit': 0, 'balance': 0}
            record.opening_debit = res['debit']
            record.opening_credit = res['credit']
            record.opening_balance = res['balance']

    @api.depends('internal_group')
    def _compute_is_off_balance(self):
        for account in self:
            account.is_off_balance = account.internal_group == "off_balance"

    def _set_opening_debit(self):
        for record in self:
            record._set_opening_debit_credit(record.opening_debit, 'debit')

    def _set_opening_credit(self):
        for record in self:
            record._set_opening_debit_credit(record.opening_credit, 'credit')

    def _set_opening_debit_credit(self, amount, field):
        """ Generic function called by both opening_debit and opening_credit's
        inverse function. 'Amount' parameter is the value to be set, and field
        either 'debit' or 'credit', depending on which one of these two fields
        got assigned.
        """
        self.company_id.create_op_move_if_non_existant()
        opening_move = self.company_id.account_opening_move_id

        if opening_move.state == 'draft':
            # check whether we should create a new move line or modify an existing one
            account_op_lines = self.env['account.move.line'].search([('account_id', '=', self.id),
                                                                      ('move_id','=', opening_move.id),
                                                                      (field,'!=', False),
                                                                      (field,'!=', 0.0)]) # 0.0 condition important for import

            if account_op_lines:
                op_aml_debit = sum(account_op_lines.mapped('debit'))
                op_aml_credit = sum(account_op_lines.mapped('credit'))

                # There might be more than one line on this account if the opening entry was manually edited
                # If so, we need to merge all those lines into one before modifying its balance
                opening_move_line = account_op_lines[0]
                if len(account_op_lines) > 1:
                    merge_write_cmd = [(1, opening_move_line.id, {'debit': op_aml_debit, 'credit': op_aml_credit, 'partner_id': None ,'name': _("Opening balance")})]
                    unlink_write_cmd = [(2, line.id) for line in account_op_lines[1:]]
                    opening_move.write({'line_ids': merge_write_cmd + unlink_write_cmd})

                if amount:
                    # modify the line
                    opening_move_line.with_context(check_move_validity=False)[field] = amount
                else:
                    # delete the line (no need to keep a line with value = 0)
                    opening_move_line.with_context(check_move_validity=False).unlink()

            elif amount:
                # create a new line, as none existed before
                self.env['account.move.line'].with_context(check_move_validity=False).create({
                        'name': _('Opening balance'),
                        field: amount,
                        'move_id': opening_move.id,
                        'account_id': self.id,
                })

            # Then, we automatically balance the opening move, to make sure it stays valid
            if not 'import_file' in self.env.context:
                # When importing a file, avoid recomputing the opening move for each account and do it at the end, for better performances
                self.company_id._auto_balance_opening_move()

    @api.model
    def default_get(self, default_fields):
        """If we're creating a new account through a many2one, there are chances that we typed the account code
        instead of its name. In that case, switch both fields values.
        """
        if 'name' not in default_fields and 'code' not in default_fields:
            return super().default_get(default_fields)
        default_name = self._context.get('default_name')
        default_code = self._context.get('default_code')
        if default_name and not default_code:
            try:
                default_code = int(default_name)
            except ValueError:
                pass
            if default_code:
                default_name = False
        contextual_self = self.with_context(default_name=default_name, default_code=default_code)
        return super(AccountAccount, contextual_self).default_get(default_fields)

    @api.model
    def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
        args = args or []
        domain = []
        if name:
            domain = ['|', ('code', '=ilike', name.split(' ')[0] + '%'), ('name', operator, name)]
            if operator in expression.NEGATIVE_TERM_OPERATORS:
                domain = ['&', '!'] + domain[1:]
        return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)

    @api.onchange('user_type_id')
    def _onchange_user_type_id(self):
        self.reconcile = self.internal_type in ('receivable', 'payable')
        if self.internal_type == 'liquidity':
            self.reconcile = False
        elif self.internal_group == 'off_balance':
            self.reconcile = False
            self.tax_ids = False
        elif self.internal_group == 'income' and not self.tax_ids:
            self.tax_ids = self.company_id.account_sale_tax_id
        elif self.internal_group == 'expense' and not self.tax_ids:
            self.tax_ids = self.company_id.account_purchase_tax_id

    def name_get(self):
        result = []
        for account in self:
            name = account.code + ' ' + account.name
            result.append((account.id, name))
        return result

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        default = dict(default or {})
        if default.get('code', False):
            return super(AccountAccount, self).copy(default)
        try:
            default['code'] = (str(int(self.code) + 10) or '').zfill(len(self.code))
            default.setdefault('name', _("%s (copy)") % (self.name or ''))
            while self.env['account.account'].search([('code', '=', default['code']),
                                                      ('company_id', '=', default.get('company_id', False) or self.company_id.id)], limit=1):
                default['code'] = (str(int(default['code']) + 10) or '')
                default['name'] = _("%s (copy)") % (self.name or '')
        except ValueError:
            default['code'] = _("%s (copy)") % (self.code or '')
            default['name'] = self.name
        return super(AccountAccount, self).copy(default)

    @api.model
    def load(self, fields, data):
        """ Overridden for better performances when importing a list of account
        with opening debit/credit. In that case, the auto-balance is postpone
        until the whole file has been imported.
        """
        rslt = super(AccountAccount, self).load(fields, data)

        if 'import_file' in self.env.context:
            companies = self.search([('id', 'in', rslt['ids'])]).mapped('company_id')
            for company in companies:
                company._auto_balance_opening_move()
        return rslt

    def _toggle_reconcile_to_true(self):
        '''Toggle the `reconcile´ boolean from False -> True

        Note that: lines with debit = credit = amount_currency = 0 are set to `reconciled´ = True
        '''
        if not self.ids:
            return None
        query = """
            UPDATE account_move_line SET
                reconciled = CASE WHEN debit = 0 AND credit = 0 AND amount_currency = 0
                    THEN true ELSE false END,
                amount_residual = (debit-credit),
                amount_residual_currency = amount_currency
            WHERE full_reconcile_id IS NULL and account_id IN %s
        """
        self.env.cr.execute(query, [tuple(self.ids)])

    def _toggle_reconcile_to_false(self):
        '''Toggle the `reconcile´ boolean from True -> False

        Note that it is disallowed if some lines are partially reconciled.
        '''
        if not self.ids:
            return None
        partial_lines_count = self.env['account.move.line'].search_count([
            ('account_id', 'in', self.ids),
            ('full_reconcile_id', '=', False),
            ('|'),
            ('matched_debit_ids', '!=', False),
            ('matched_credit_ids', '!=', False),
        ])
        if partial_lines_count > 0:
            raise UserError(_('You cannot switch an account to prevent the reconciliation '
                              'if some partial reconciliations are still pending.'))
        query = """
            UPDATE account_move_line
                SET amount_residual = 0, amount_residual_currency = 0
            WHERE full_reconcile_id IS NULL AND account_id IN %s
        """
        self.env.cr.execute(query, [tuple(self.ids)])

    def write(self, vals):
        # Do not allow changing the company_id when account_move_line already exist
        if vals.get('company_id', False):
            move_lines = self.env['account.move.line'].search([('account_id', 'in', self.ids)], limit=1)
            for account in self:
                if (account.company_id.id != vals['company_id']) and move_lines:
                    raise UserError(_('You cannot change the owner company of an account that already contains journal items.'))
        if 'reconcile' in vals:
            if vals['reconcile']:
                self.filtered(lambda r: not r.reconcile)._toggle_reconcile_to_true()
            else:
                self.filtered(lambda r: r.reconcile)._toggle_reconcile_to_false()

        if vals.get('currency_id'):
            for account in self:
                if self.env['account.move.line'].search_count([('account_id', '=', account.id), ('currency_id', 'not in', (False, vals['currency_id']))]):
                    raise UserError(_('You cannot set a currency on this account as it already has some journal entries having a different foreign currency.'))

        return super(AccountAccount, self).write(vals)

    @api.ondelete(at_uninstall=False)
    def _unlink_except_contains_journal_items(self):
        if self.env['account.move.line'].search([('account_id', 'in', self.ids)], limit=1):
            raise UserError(_('You cannot perform this action on an account that contains journal items.'))

    @api.ondelete(at_uninstall=False)
    def _unlink_except_account_set_on_customer(self):
        #Checking whether the account is set as a property to any Partner or not
        values = ['account.account,%s' % (account_id,) for account_id in self.ids]
        partner_prop_acc = self.env['ir.property'].sudo().search([('value_reference', 'in', values)], limit=1)
        if partner_prop_acc:
            account_name = partner_prop_acc.get_by_record().display_name
            raise UserError(
                _('You cannot remove/deactivate the account %s which is set on a customer or vendor.', account_name)
            )

    @api.ondelete(at_uninstall=False)
    def _unlink_except_linked_to_fiscal_position(self):
        if self.env['account.fiscal.position.account'].search(['|', ('account_src_id', 'in', self.ids), ('account_dest_id', 'in', self.ids)], limit=1):
            raise UserError(_('You cannot remove/deactivate the accounts "%s" which are set on the account mapping of a fiscal position.', ', '.join(f"{a.code} - {a.name}" for a in self)))

    @api.ondelete(at_uninstall=False)
    def _unlink_except_linked_to_tax_repartition_line(self):
        if self.env['account.tax.repartition.line'].search([('account_id', 'in', self.ids)], limit=1):
            raise UserError(_('You cannot remove/deactivate the accounts "%s" which are set on a tax repartition line.', ', '.join(f"{a.code} - {a.name}" for a in self)))

    def action_read_account(self):
        self.ensure_one()
        return {
            'name': self.display_name,
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'account.account',
            'res_id': self.id,
        }

    def action_duplicate_accounts(self):
        for account in self.browse(self.env.context['active_ids']):
            account.copy()

    def action_open_related_taxes(self):
        related_taxes_ids = self.env['account.tax'].search([
            '|',
            ('invoice_repartition_line_ids.account_id', '=', self.id),
            ('refund_repartition_line_ids.account_id', '=', self.id),
        ]).ids
        return {
            'type': 'ir.actions.act_window',
            'name': _('Taxes'),
            'res_model': 'account.tax',
            'view_type': 'list',
            'view_mode': 'list',
            'views': [[False, 'list'], [False, 'form']],
            'domain': [('id', 'in', related_taxes_ids)],
        }
class AccountRegisterPayments(models.TransientModel):
    _inherit = 'account.register.payments'

    @api.onchange('journal_id')
    def onchange_journal(self):
        res = super(AccountRegisterPayments, self)._onchange_journal()
        res['value'] = {'check_ids': []}
        return res

    @api.onchange('payment_type')
    def _onchange_payment_type(self):
        # 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

    check_ids = fields.Many2many(
        'account.check',
        string='Cheques',
        copy=False,
    )
    # only for v8 comatibility where more than one check could be received
    # or ownd
    check_ids_copy = fields.Many2many(
        related='check_ids',
        readonly=True,
    )
    readonly_currency_id = fields.Many2one(
        related='currency_id',
        readonly=True,
    )
    readonly_amount = fields.Monetary(
        related='amount',
        readonly=True,
    )
    # we add this field for better usability on own 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='Cheque',
    )

    @api.multi
    @api.depends('check_ids')
    def _compute_check(self):
        for rec in self:
            # we only show checks for own checks or received thid checks
            # if len of checks is 1
            if rec.payment_method_code in (
                    'received_third_check',
                    'own_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(
        'Nombre del cheque',
        copy=False,
    )
    check_number = fields.Integer('Número del cheque', copy=False)
    check_own_date = fields.Date(
        'Fecha del cheque',
        copy=False,
        default=fields.Date.context_today,
    )
    check_payment_date = fields.Date(
        'Fecha del pago de cheque',
        help="Only if this check is post dated",
    )
    checkbook_id = fields.Many2one(
        'account.checkbook',
        'Chequera',
    )
    check_subtype = fields.Selection(
        related='checkbook_id.own_check_subtype',
        readonly=True,
    )
    check_bank_id = fields.Many2one(
        'res.bank',
        'Banco del cheque',
        copy=False,
    )
    check_owner_vat = fields.Char(
        'Vat del emisor',
        copy=False,
    )
    check_owner_name = fields.Char(
        'Emisor',
        copy=False,
    )
    # this fields is to help with code and view
    check_type = fields.Char(compute='_compute_check_type', )
    checkbook_block_manual_number = fields.Boolean(
        related='checkbook_id.block_manual_number', )
    check_number_readonly = fields.Integer(
        related='check_number',
        readonly=True,
    )
    operation_no = fields.Char(string='Operation No.')

    @api.multi
    @api.depends('payment_method_code')
    def _compute_check_type(self):
        for rec in self:
            if rec.payment_method_code == 'own_check':
                rec.check_type = 'own_check'
            elif rec.payment_method_code in [
                    'received_third_check', 'received_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 == 'received_third_check':
            self.amount = sum(self.check_ids.mapped('amount'))

    @api.multi
    @api.onchange('check_number')
    def change_check_number(self):
        # TODO make default padding a parameter
        def _get_name_from_number(number):
            padding = 8
            if len(str(number)) > padding:
                padding = len(str(number))
            return ('%%0%sd' % padding % number)

        for rec in self:
            if rec.payment_method_code in ['received_third_check']:
                if not rec.check_number:
                    check_name = False
                else:
                    check_name = _get_name_from_number(rec.check_number)
                rec.check_name = check_name
            elif rec.payment_method_code in ['own_check']:
                sequence = rec.checkbook_id.sequence_id
                if not rec.check_number:
                    check_name = False
                elif sequence:
                    if rec.check_number != sequence.number_next_actual:
                        sequence.write(
                            {'number_next_actual': rec.check_number})
                    check_name = rec.checkbook_id.sequence_id.next_by_id()
                else:
                    # in sipreco, for eg, no sequence on checkbooks
                    check_name = _get_name_from_number(rec.check_number)
                rec.check_name = check_name

    @api.onchange('check_own_date', 'check_payment_date')
    def onchange_date(self):
        if (self.check_own_date and self.check_payment_date
                and self.check_own_date > self.check_payment_date):
            self.check_payment_date = False
            raise UserError(
                _('Fecha de pago del cheque debe ser mayor a fecha del cheque')
            )

    @api.onchange('partner_id', 'payment_method_code')
    def onchange_partner_check(self):
        commercial_partner = self.partner_id.commercial_partner_id
        #Modificacion para v10. Preguntamos existencia de campo payment_method_code
        if self.payment_method_code:
            if self.payment_method_code == 'received_third_check':
                self.check_bank_id = (commercial_partner.bank_ids and
                                      commercial_partner.bank_ids[0].bank_id
                                      or False)
                self.check_owner_name = commercial_partner.name
                vat_field = 'vat'
                # to avoid needed of another module, we add this check to see
                # if l10n_ar cuit field is available
                if 'cuit' in commercial_partner._fields:
                    vat_field = 'cuit'
                self.check_owner_vat = commercial_partner[vat_field]
            elif self.payment_method_code == 'own_check':
                self.check_bank_id = self.journal_id.bank_id
                self.check_owner_name = False
                self.check_owner_vat = False

    @api.onchange('payment_method_code')
    def _onchange_payment_method_code(self):
        if self.payment_method_code == 'own_check':
            checkbook = self.env['account.checkbook'].search(
                [('state', '=', 'active'),
                 ('journal_id', '=', self.journal_id.id)],
                limit=1)
            self.checkbook_id = checkbook
        elif self.checkbook_id:
            # TODO ver si interesa implementar volver atras numeracion
            self.checkbook_id = False

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

    @api.multi
    def create_payment(self):
        ctx = dict(self._context)
        payment_register_wzd = self
        ctx['register_payment'] = True
        ctx['payment_register_wzd'] = payment_register_wzd
        super(AccountRegisterPayments, self.with_context(ctx)).create_payment()

    def get_payment_vals(self):
        vals = super(AccountRegisterPayments, self).get_payment_vals()
        vals.update({
            'check_ids': [(4, check.id, None) for check in self.check_ids],
            'check_id':
            self.check_id.id,
            'check_name':
            self.check_name,
            'check_number':
            self.check_number,
            'check_own_date':
            self.check_own_date,
            'check_payment_date':
            self.check_payment_date,
            'checkbook_id':
            self.checkbook_id.id,
            'check_bank_id':
            self.check_bank_id.id,
            'check_owner_vat':
            self.check_owner_vat,
            'check_owner_name':
            self.check_owner_name,
        })
        return vals
Exemple #20
0
class manifest_pembayaran(models.Model):
    _name = "manifest.pembayaran"

    name = fields.Char('Reference', readonly=True, default='/')
    user_id = fields.Many2one('res.users',
                              'Operator',
                              readonly=True,
                              required=True,
                              default=lambda self: self.env.user,
                              copy=False)
    date = fields.Date('Tanggal',
                       readonly=True,
                       required=True,
                       states={'draft': [('readonly', False)]},
                       default=fields.Date.context_today)
    siswa_id = fields.Many2one('res.partner',
                               'Siswa',
                               domain=[('student', '=', True)],
                               readonly=True,
                               states={'draft': [('readonly', False)]})
    orangtua_id = fields.Many2one('res.partner',
                                  'Orang Tua',
                                  domain=[('parent', '=', True)],
                                  readonly=True,
                                  states={'draft': [('readonly', False)]})
    tagihan_ids = fields.Many2many('account.invoice',
                                   'tagihan_rel',
                                   'manifest_id',
                                   'tagihan_id',
                                   'Invoice',
                                   domain=[('type', '=', 'out_invoice')],
                                   readonly=True,
                                   states={'draft': [('readonly', False)]})
    state = fields.Selection(compute='compute_state',
                             selection=[('draft', 'Draft'), ('paid', 'Paid')],
                             string='Status',
                             default='draft',
                             store=True)
    currency_id = fields.Many2one("res.currency",
                                  string="Currency",
                                  compute='_compute_currency_id')
    amount_total = fields.Monetary(string='Total',
                                   store=True,
                                   readonly=True,
                                   compute='_amount_all')

    @api.model
    def create(self, vals):
        vals['name'] = self.env['ir.sequence'].next_by_code(
            'manifest.pembayaran') or '/'
        result = super(manifest_pembayaran, self).create(vals)
        return result

    @api.multi
    def unlink(self):
        for o in self:
            if o.state == 'paid':
                raise UserError((
                    'Manifest pembayaran tidak bisa dihapus pada status PAID !'
                ))
        return super(manifest_pembayaran, self).unlink()

    @api.onchange('orangtua_id', 'siswa_id')
    def onchange_orangtua_siswa(self):
        value = {}
        domain_tagihan = [('state', '=', 'open'), ('type', '=', 'out_invoice')]
        if self.siswa_id:
            value = {'orangtua_id': self.siswa_id.orangtua_id.id}
            domain_tagihan.append(('partner_id', '=', self.siswa_id.id))
        if self.orangtua_id:
            # value = {'orangtua_id': self.siswa_id.orangtua_id.id}
            domain_tagihan.append(('orangtua_id', '=', self.orangtua_id.id))
        return {'domain': {'tagihan_ids': domain_tagihan}, 'value': value}

    @api.depends('tagihan_ids.amount_total')
    def _amount_all(self):
        for o in self:
            total = 0
            for i in o.tagihan_ids:
                total += i.amount_total
            o.update({
                'amount_total': total,
            })

    @api.multi
    def _compute_currency_id(self):
        try:
            main_company = self.sudo().env.ref('base.main_company')
        except ValueError:
            main_company = self.env['res.company'].sudo().search([],
                                                                 limit=1,
                                                                 order="id")
        for template in self:
            template.currency_id = main_company.currency_id.id

    @api.depends('tagihan_ids.state')
    def compute_state(self):
        for payment in self:
            if len(
                    set([
                        i.state for i in payment.tagihan_ids
                        if i.state == 'paid'
                    ])) == 1:
                payment.state = 'paid'
            else:
                payment.state = 'draft'

    @api.multi
    def proses_pembayaran(self):
        for i in self.tagihan_ids:
            if i.state == 'draft':
                raise UserError(
                    ('Invoice status draft harus di validate terlebih dahulu'))
                # i.action_invoice_open()
            elif i.state == 'paid':
                raise UserError(
                    ('Invoice yang sudah paid tidak bisa diproses'))

        return {
            'name': _('Manifest Pembayaaran'),
            'type': 'ir.actions.act_window',
            'res_model': 'account.register.payments',
            'view_type': 'form',
            'view_mode': 'form',
            'target': 'new',
            'context': {
                'active_ids': [x.id for x in self.tagihan_ids],
                'active_model': 'account.invoice',
            }
        }

    @api.multi
    def print_manifest(self):
        return self.env.ref(
            'alhamra_akademik.action_report_manifest').report_action(self)
Exemple #21
0
class PosDebtReport(models.Model):

    _name = "report.pos.debt"
    _inherit = ['base_groupby_extra']
    _description = "POS Debt Statistics"
    _auto = False
    _order = 'date desc'

    order_id = fields.Many2one('pos.order', string='POS Order', readonly=True)
    invoice_id = fields.Many2one('account.invoice',
                                 string='Invoice',
                                 readonly=True)
    update_id = fields.Many2one('pos.credit.update',
                                string='Manual Update',
                                readonly=True)

    date = fields.Datetime(string='Date', readonly=True)
    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 readonly=True)
    user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
    session_id = fields.Many2one('pos.session',
                                 string='Session',
                                 readonly=True)
    config_id = fields.Many2one('pos.config', string='POS', readonly=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True)
    currency_id = fields.Many2one('res.currency',
                                  string='Currency',
                                  readonly=True)

    state = fields.Selection([('open', 'Open'), ('confirm', 'Validated')],
                             readonly=True)
    credit_product = fields.Boolean(
        'Credit Product',
        help="Record is registered as Purchasing credit product",
        readonly=True)
    balance = fields.Monetary(
        'Balance',
        help=
        "Negative value for purchases without money (debt). Positive for credit payments (prepament or payments for debts).",
        readonly=True)
    product_list = fields.Text('Product List', readonly=True)

    @api.model_cr
    def init(self):
        tools.drop_view_if_exists(self._cr, 'report_pos_debt')
        self._cr.execute("""
            CREATE OR REPLACE VIEW report_pos_debt AS (
                (
                SELECT
                    st_line.id as id,
                    o.id as order_id,
                    NULL::integer as invoice_id,
                    NULL::integer as update_id,
                    -st_line.amount as balance,
                    st.state as state,
                    false as credit_product,

                    o.date_order as date,
                    o.partner_id as partner_id,
                    o.user_id as user_id,
                    o.session_id as session_id,
                    session.config_id as config_id,
                    o.company_id as company_id,
                    pricelist.currency_id as currency_id,
                    o.product_list as product_list

                FROM account_bank_statement_line as st_line
                    LEFT JOIN account_bank_statement st ON (st.id=st_line.statement_id)
                    LEFT JOIN account_journal journal ON (journal.id=st.journal_id)
                    LEFT JOIN pos_order o ON (o.id=st_line.pos_statement_id)

                    LEFT JOIN pos_session session ON (session.id=o.session_id)
                    LEFT JOIN product_pricelist pricelist ON (pricelist.id=o.pricelist_id)
                WHERE
                    journal.debt=true
                )
                UNION ALL
                (
                SELECT
                    -pos_line.id as id,
                    o.id as order_id,
                    NULL::integer as invoice_id,
                    NULL::integer as update_id,
                    pos_line.price_unit * qty as balance,
                    CASE o.state
                        WHEN 'done' THEN 'confirm'
                        WHEN 'paid' THEN 'open'
                        ELSE o.state
                    END as state,
                    true as credit_product,

                    o.date_order as date,
                    o.partner_id as partner_id,
                    o.user_id as user_id,
                    o.session_id as session_id,
                    session.config_id as config_id,
                    o.company_id as company_id,
                    pricelist.currency_id as currency_id,
                    o.product_list as product_list

                FROM pos_order_line as pos_line
                    LEFT JOIN product_product pp ON (pp.id=pos_line.product_id)
                    LEFT JOIN product_template pt ON (pt.id=pp.product_tmpl_id)

                    LEFT JOIN pos_order o ON (o.id=pos_line.order_id)

                    LEFT JOIN pos_session session ON (session.id=o.session_id)
                    LEFT JOIN product_pricelist pricelist ON (pricelist.id=o.pricelist_id)
                WHERE
                    pt.credit_product=true
                    AND o.state IN ('paid','done')

                )
                UNION ALL
                (
                SELECT
                    (2147483647 - inv_line.id) as id,
                    NULL::integer as order_id,
                    inv.id as invoice_id,
                    NULL::integer as update_id,
                    inv_line.price_subtotal as balance,
                    'confirm' as state,
                    true as credit_product,

                    inv.date_invoice as date,
                    inv.partner_id as partner_id,
                    inv.user_id as user_id,
                    NULL::integer as session_id,
                    NULL::integer as config_id,
                    inv.company_id as company_id,
                    inv.currency_id as currency_id,
                    '' as product_list

                FROM account_invoice_line as inv_line
                    LEFT JOIN product_product pp ON (pp.id=inv_line.product_id)
                    LEFT JOIN product_template pt ON (pt.id=pp.product_tmpl_id)
                    LEFT JOIN account_invoice inv ON (inv.id=inv_line.invoice_id)
                WHERE
                    pt.credit_product=true
                    AND inv.state in ('paid')
                )
                UNION ALL
                (
                SELECT
                    (-2147483647 + record.id) as id,
                    NULL::integer as order_id,
                    NULL::integer as invoice_id,
                    record.id as update_id,
                    record.balance as balance,
                    record.state as state,
                    false as credit_product,

                    record.date as date,
                    record.partner_id as partner_id,
                    record.user_id as user_id,
                    NULL::integer as session_id,
                    NULL::integer as config_id,
                    record.company_id as company_id,
                    record.currency_id as currency_id,
                    record.note as product_list

                FROM pos_credit_update as record
                WHERE
                    record.state in ('confirm')
                )
            )
        """)
class Document(models.Model):
    """ Implementação base dos documentos fiscais

    Devemos sempre ter em mente que o modelo que vai usar este módulo abstrato
     tem diversos metodos importantes e a intenção que os módulos da OCA que
     extendem este modelo, funcionem se possível sem a necessidade de
     codificação extra.

    É preciso também estar atento que o documento fiscal tem dois estados:

    - Estado do documento eletrônico / não eletônico: state_edoc
    - Estado FISCAL: state_fiscal

    O estado fiscal é um campo que é alterado apenas algumas vezes pelo código
    e é de responsabilidade do responsável fiscal pela empresa de manter a
    integridade do mesmo, pois ele não tem um fluxo realmente definido e
    interfere no lançamento do registro no arquivo do SPED FISCAL.
    """
    _name = 'l10n_br_fiscal.document'
    _inherit = [
        'mail.thread', 'mail.activity.mixin', 'l10n_br_fiscal.document.mixin',
        'l10n_br_fiscal.document.electronic'
    ]
    _description = 'Fiscal Document'

    @api.depends('line_ids')
    def _compute_amount(self):
        for record in self:
            record.amount_untaxed = sum(line.amount_untaxed
                                        for line in record.line_ids)
            record.amount_icms_base = sum(line.icms_base
                                          for line in record.line_ids)
            record.amount_icms_value = sum(line.icms_value
                                           for line in record.line_ids)
            record.amount_icmssn_value = sum(line.icmssn_credit_value
                                             for line in record.line_ids)
            record.amount_ipi_base = sum(line.ipi_base
                                         for line in record.line_ids)
            record.amount_ipi_value = sum(line.ipi_value
                                          for line in record.line_ids)
            record.amount_pis_base = sum(line.pis_base
                                         for line in record.line_ids)
            record.amount_pis_value = sum(line.pis_value
                                          for line in record.line_ids)
            record.amount_pis_ret_base = sum(line.pis_wh_base
                                             for line in record.line_ids)
            record.amount_pis_ret_value = sum(line.pis_wh_value
                                              for line in record.line_ids)
            record.amount_cofins_base = sum(line.cofins_base
                                            for line in record.line_ids)
            record.amount_cofins_value = sum(line.cofins_value
                                             for line in record.line_ids)
            record.amount_cofins_ret_base = sum(line.cofins_wh_base
                                                for line in record.line_ids)
            record.amount_cofins_ret_value = sum(line.cofins_wh_value
                                                 for line in record.line_ids)
            record.amount_csll_base = sum(line.csll_base
                                          for line in record.line_ids)
            record.amount_csll_value = sum(line.csll_value
                                           for line in record.line_ids)
            record.amount_csll_ret_base = sum(line.csll_wh_base
                                              for line in record.line_ids)
            record.amount_csll_ret_value = sum(line.csll_wh_value
                                               for line in record.line_ids)
            record.amount_issqn_base = sum(line.issqn_base
                                           for line in record.line_ids)
            record.amount_issqn_value = sum(line.issqn_value
                                            for line in record.line_ids)
            record.amount_issqn_ret_base = sum(line.issqn_wh_base
                                               for line in record.line_ids)
            record.amount_issqn_ret_value = sum(line.issqn_wh_value
                                                for line in record.line_ids)
            record.amount_irpj_base = sum(line.irpj_base
                                          for line in record.line_ids)
            record.amount_irpj_value = sum(line.irpj_value
                                           for line in record.line_ids)
            record.amount_irpj_ret_base = sum(line.irpj_wh_base
                                              for line in record.line_ids)
            record.amount_irpj_ret_value = sum(line.irpj_wh_value
                                               for line in record.line_ids)
            record.amount_inss_base = sum(line.inss_base
                                          for line in record.line_ids)
            record.amount_inss_value = sum(line.inss_value
                                           for line in record.line_ids)
            record.amount_inss_wh_base = sum(line.inss_wh_base
                                             for line in record.line_ids)
            record.amount_inss_wh_value = sum(line.inss_wh_value
                                              for line in record.line_ids)
            record.amount_tax = sum(line.amount_tax
                                    for line in record.line_ids)
            record.amount_discount = sum(line.discount_value
                                         for line in record.line_ids)
            record.amount_insurance_value = sum(line.insurance_value
                                                for line in record.line_ids)
            record.amount_other_costs_value = sum(line.other_costs_value
                                                  for line in record.line_ids)
            record.amount_freight_value = sum(line.freight_value
                                              for line in record.line_ids)
            record.amount_total = sum(line.amount_total
                                      for line in record.line_ids)
            record.amount_financial = sum(line.amount_financial
                                          for line in record.line_ids)
            record.amount_tax_withholding = sum(line.amount_tax_withholding
                                                for line in record.line_ids)
            record.amount_estimate_tax = sum(line.amount_estimate_tax
                                             for line in record.line_ids)

    # used mostly to enable _inherits of account.invoice on
    # fiscal_document when existing invoices have no fiscal document.
    active = fields.Boolean(
        string='Active',
        default=True,
    )

    fiscal_operation_id = fields.Many2one(
        domain="[('state', '=', 'approved'), "
        "'|', ('fiscal_operation_type', '=', fiscal_operation_type),"
        " ('fiscal_operation_type', '=', 'all')]", )

    fiscal_operation_type = fields.Selection(related=False, )

    is_edoc_printed = fields.Boolean(
        string='Printed',
        readonly=True,
    )

    number = fields.Char(
        string='Number',
        copy=False,
        index=True,
    )

    key = fields.Char(
        string='key',
        copy=False,
        index=True,
    )

    issuer = fields.Selection(
        selection=DOCUMENT_ISSUER,
        default=DOCUMENT_ISSUER_COMPANY,
        required=True,
        string='Issuer',
    )

    date = fields.Datetime(
        string='Date',
        copy=False,
    )

    user_id = fields.Many2one(
        comodel_name='res.users',
        string='User',
        index=True,
        default=lambda self: self.env.user,
    )

    document_type_id = fields.Many2one(
        comodel_name='l10n_br_fiscal.document.type', )

    operation_name = fields.Char(
        string='Operation Name',
        copy=False,
    )

    document_electronic = fields.Boolean(
        related='document_type_id.electronic',
        string='Electronic?',
        store=True,
    )

    date_in_out = fields.Datetime(
        string='Date Move',
        copy=False,
    )

    document_serie_id = fields.Many2one(
        comodel_name='l10n_br_fiscal.document.serie',
        domain="[('active', '=', True),"
        "('document_type_id', '=', document_type_id)]",
    )

    document_serie = fields.Char(string='Serie Number', )

    fiscal_document_related_ids = fields.One2many(
        comodel_name='l10n_br_fiscal.document.related',
        inverse_name='fiscal_document_id',
        string='Fiscal Document Related',
    )

    partner_id = fields.Many2one(
        comodel_name='res.partner',
        string='Partner',
    )

    partner_legal_name = fields.Char(
        string='Legal Name',
        related='partner_id.legal_name',
    )

    partner_name = fields.Char(
        string='Name',
        related='partner_id.name',
    )

    partner_cnpj_cpf = fields.Char(
        string='CNPJ',
        related='partner_id.cnpj_cpf',
    )

    partner_inscr_est = fields.Char(
        string='State Tax Number',
        related='partner_id.inscr_est',
    )

    partner_ind_ie_dest = fields.Selection(
        selection=NFE_IND_IE_DEST,
        string='Contribuinte do ICMS',
        related='partner_id.ind_ie_dest',
    )

    partner_inscr_mun = fields.Char(
        string='Municipal Tax Number',
        related='partner_id.inscr_mun',
    )

    partner_suframa = fields.Char(
        string='Suframa',
        related='partner_id.suframa',
    )

    partner_cnae_main_id = fields.Many2one(
        comodel_name='l10n_br_fiscal.cnae',
        string='Main CNAE',
        related='partner_id.cnae_main_id',
    )

    partner_tax_framework = fields.Selection(
        selection=TAX_FRAMEWORK,
        string='Tax Framework',
        related='partner_id.tax_framework',
    )

    partner_street = fields.Char(
        string='Partner Street',
        related='partner_id.street',
    )

    partner_number = fields.Char(
        string='Partner Number',
        related='partner_id.street_number',
    )

    partner_street2 = fields.Char(
        string='Partner Street2',
        related='partner_id.street2',
    )

    partner_district = fields.Char(
        string='Partner District',
        related='partner_id.district',
    )

    partner_country_id = fields.Many2one(
        comodel_name='res.country',
        string='Partner Country',
        related='partner_id.country_id',
    )

    partner_state_id = fields.Many2one(
        comodel_name='res.country.state',
        string='Partner State',
        related='partner_id.state_id',
    )

    partner_city_id = fields.Many2one(
        comodel_name='res.city',
        string='Partner City',
        related='partner_id.city_id',
    )

    partner_zip = fields.Char(
        string='Partner Zip',
        related='partner_id.zip',
    )

    partner_phone = fields.Char(
        string='Partner Phone',
        related='partner_id.phone',
    )

    partner_is_company = fields.Boolean(
        string='Partner Is Company?',
        related='partner_id.is_company',
    )

    partner_shipping_id = fields.Many2one(
        comodel_name='res.partner',
        string='Shipping Address',
    )

    company_id = fields.Many2one(
        comodel_name='res.company',
        string='Company',
        default=lambda self: self.env['res.company']._company_default_get(
            'l10n_br_fiscal.document'),
    )

    processador_edoc = fields.Selection(
        related='company_id.processador_edoc',
        store=True,
    )

    company_legal_name = fields.Char(
        string='Company Legal Name',
        related='company_id.legal_name',
    )

    company_name = fields.Char(
        string='Company Name',
        size=128,
        related='company_id.name',
    )

    company_cnpj_cpf = fields.Char(
        string='Company CNPJ',
        related='company_id.cnpj_cpf',
    )

    company_inscr_est = fields.Char(
        string='Company State Tax Number',
        related='company_id.inscr_est',
    )

    company_inscr_mun = fields.Char(
        string='Company Municipal Tax Number',
        related='company_id.inscr_mun',
    )

    company_suframa = fields.Char(
        string='Company Suframa',
        related='company_id.suframa',
    )

    company_cnae_main_id = fields.Many2one(
        comodel_name='l10n_br_fiscal.cnae',
        string='Company Main CNAE',
        related='company_id.cnae_main_id',
    )

    company_tax_framework = fields.Selection(
        selection=TAX_FRAMEWORK,
        string='Company Tax Framework',
        related='company_id.tax_framework',
    )

    company_street = fields.Char(
        string='Company Street',
        related='company_id.street',
    )

    company_number = fields.Char(
        string='Company Number',
        related='company_id.street_number',
    )

    company_street2 = fields.Char(
        string='Company Street2',
        related='company_id.street2',
    )
    company_district = fields.Char(
        string='Company District',
        related='company_id.district',
    )

    company_country_id = fields.Many2one(
        comodel_name='res.country',
        string='Company Country',
        related='company_id.country_id',
    )

    company_state_id = fields.Many2one(
        comodel_name='res.country.state',
        string='Company State',
        related='company_id.state_id',
    )

    company_city_id = fields.Many2one(
        comodel_name='res.city',
        string='Company City',
        related='company_id.city_id',
    )
    company_zip = fields.Char(
        string='Company ZIP',
        related='company_id.zip',
    )

    company_phone = fields.Char(
        string='Company Phone',
        related='company_id.phone',
    )

    currency_id = fields.Many2one(
        comodel_name='res.currency',
        default=lambda self: self.env.user.company_id.currency_id,
        store=True,
        readonly=True,
    )

    amount_untaxed = fields.Monetary(
        string='Amount Untaxed',
        compute='_compute_amount',
    )

    amount_icms_base = fields.Monetary(
        string='ICMS Base',
        compute='_compute_amount',
    )

    amount_icms_value = fields.Monetary(
        string='ICMS Value',
        compute='_compute_amount',
    )

    amount_icmssn_value = fields.Monetary(
        string='ICMSSN Value',
        compute='_compute_amount',
    )

    amount_ipi_base = fields.Monetary(
        string='IPI Base',
        compute='_compute_amount',
    )

    amount_ipi_value = fields.Monetary(
        string='IPI Value',
        compute='_compute_amount',
    )

    amount_pis_base = fields.Monetary(
        string='PIS Base',
        compute='_compute_amount',
    )

    amount_pis_value = fields.Monetary(
        string='PIS Value',
        compute='_compute_amount',
    )

    amount_pis_ret_base = fields.Monetary(
        string='PIS Ret Base',
        compute='_compute_amount',
    )

    amount_pis_ret_value = fields.Monetary(
        string='PIS Ret Value',
        compute='_compute_amount',
    )

    amount_cofins_base = fields.Monetary(
        string='COFINS Base',
        compute='_compute_amount',
    )

    amount_cofins_value = fields.Monetary(
        string='COFINS Value',
        compute='_compute_amount',
    )

    amount_cofins_ret_base = fields.Monetary(
        string='COFINS Ret Base',
        compute='_compute_amount',
    )

    amount_cofins_ret_value = fields.Monetary(
        string='COFINS Ret Value',
        compute='_compute_amount',
    )

    amount_issqn_base = fields.Monetary(
        string='ISSQN Base',
        compute='_compute_amount',
    )

    amount_issqn_value = fields.Monetary(
        string='ISSQN Value',
        compute='_compute_amount',
    )

    amount_issqn_ret_base = fields.Monetary(
        string='ISSQN Ret Base',
        compute='_compute_amount',
    )

    amount_issqn_ret_value = fields.Monetary(
        string='ISSQN Ret Value',
        compute='_compute_amount',
    )

    amount_csll_base = fields.Monetary(
        string='CSLL Base',
        compute='_compute_amount',
    )

    amount_csll_value = fields.Monetary(
        string='CSLL Value',
        compute='_compute_amount',
    )

    amount_csll_ret_base = fields.Monetary(
        string='CSLL Ret Base',
        compute='_compute_amount',
    )

    amount_csll_ret_value = fields.Monetary(
        string='CSLL Ret Value',
        compute='_compute_amount',
    )

    amount_irpj_base = fields.Monetary(
        string='IRPJ Base',
        compute='_compute_amount',
    )

    amount_irpj_value = fields.Monetary(
        string='IRPJ Value',
        compute='_compute_amount',
    )

    amount_irpj_ret_base = fields.Monetary(
        string='IRPJ Ret Base',
        compute='_compute_amount',
    )

    amount_irpj_ret_value = fields.Monetary(
        string='IRPJ Ret Value',
        compute='_compute_amount',
    )

    amount_inss_base = fields.Monetary(
        string='INSS Base',
        compute='_compute_amount',
    )

    amount_inss_value = fields.Monetary(
        string='INSS Value',
        compute='_compute_amount',
    )

    amount_inss_wh_base = fields.Monetary(
        string='INSS Ret Base',
        compute='_compute_amount',
    )

    amount_inss_wh_value = fields.Monetary(
        string='INSS Ret Value',
        compute='_compute_amount',
    )

    amount_estimate_tax = fields.Monetary(
        string='Amount Estimate Tax',
        compute='_compute_amount',
    )

    amount_tax = fields.Monetary(
        string='Amount Tax',
        compute='_compute_amount',
    )

    amount_total = fields.Monetary(
        string='Amount Total',
        compute='_compute_amount',
    )

    amount_tax_withholding = fields.Monetary(string="Amount Tax Withholding",
                                             compute='_compute_amount')

    amount_financial = fields.Monetary(
        string='Amount Financial',
        compute='_compute_amount',
    )

    amount_discount = fields.Monetary(
        string='Amount Discount',
        compute='_compute_amount',
    )

    amount_insurance_value = fields.Monetary(
        string='Insurance Value',
        default=0.00,
        compute='_compute_amount',
    )

    amount_other_costs_value = fields.Monetary(
        string='Other Costs',
        default=0.00,
        compute='_compute_amount',
    )

    amount_freight_value = fields.Monetary(
        string='Freight Value',
        default=0.00,
        compute='_compute_amount',
    )

    line_ids = fields.One2many(
        comodel_name='l10n_br_fiscal.document.line',
        inverse_name='document_id',
        string='Document Lines',
        copy=True,
    )

    # TODO esse campo deveria ser calculado de
    # acordo com o fiscal_document_id
    document_section = fields.Selection(
        selection=[
            ('nfe', 'NF-e'),
            ('nfse_recibos', 'NFS-e e Recibos'),
            ('nfce_cfe', 'NFC-e e CF-e'),
            ('cte', 'CT-e'),
            ('todos', 'Todos'),
        ],
        string='Document Section',
        readonly=True,
        copy=True,
    )

    edoc_purpose = fields.Selection(
        selection=[
            ('1', 'Normal'),
            ('2', 'Complementar'),
            ('3', 'Ajuste'),
            ('4', 'Devolução de mercadoria'),
        ],
        string='Finalidade',
        default='1',
    )

    document_event_ids = fields.One2many(
        comodel_name='l10n_br_fiscal.document.event',
        inverse_name='fiscal_document_id',
        string='Events',
        copy=False,
        readonly=True,
    )

    close_id = fields.Many2one(comodel_name='l10n_br_fiscal.closing',
                               string='Close ID')

    document_type = fields.Char(
        related='document_type_id.code',
        stored=True,
    )

    dfe_id = fields.Many2one(
        comodel_name='l10n_br_fiscal.dfe',
        string='DF-e Consult',
    )

    # Você não vai poder fazer isso em modelos que já tem state
    # TODO Porque não usar o campo state do fiscal.document???
    state = fields.Selection(related="state_edoc", string='State')

    document_subsequent_ids = fields.One2many(
        comodel_name='l10n_br_fiscal.subsequent.document',
        inverse_name='source_document_id',
        copy=True,
    )

    document_subsequent_generated = fields.Boolean(
        string='Subsequent documents generated?',
        compute='_compute_document_subsequent_generated',
        default=False,
    )

    @api.multi
    @api.constrains('number')
    def _check_number(self):
        for record in self:
            if not record.number:
                return
            domain = [('id', '!=', record.id), ('active', '=', True),
                      ('company_id', '=', record.company_id.id),
                      ('issuer', '=', record.issuer),
                      ('document_type_id', '=', record.document_type_id.id),
                      ('document_serie', '=', record.document_serie),
                      ('number', '=', record.number)]

            if record.issuer == DOCUMENT_ISSUER_PARTNER:
                domain.append(('partner_id', '=', record.partner_id.id))

            if record.env["l10n_br_fiscal.document"].search(domain):
                raise ValidationError(
                    _("There is already a fiscal document with this "
                      "Serie: {0}, Number: {1} !".format(
                          record.document_serie, record.number)))

    @api.multi
    def name_get(self):
        return [(r.id, '{0} - Série: {1} - Número: {2}'.format(
            r.document_type_id.name, r.document_serie, r.number))
                for r in self]

    @api.model
    def create(self, values):
        if not values.get('date'):
            values['date'] = self._date_server_format()
        return super().create(values)

    @api.onchange('company_id')
    def _onchange_company_id(self):
        if self.company_id:
            self.currency_id = self.company_id.currency_id

    @api.multi
    @api.onchange('document_section')
    def _onchange_document_section(self):
        if self.document_section:
            domain = dict()
            if self.document_section == 'nfe':
                domain['document_type_id'] = [('code', '=', '55')]
                self.document_type_id = \
                    self.env['l10n_br_fiscal.document.type'].search([
                        ('code', '=', '55')
                    ])[0]
            elif self.document_section == 'nfse_recibos':
                domain['document_type_id'] = [('code', '=', 'SE')]
                self.document_type_id = \
                    self.env['l10n_br_fiscal.document.type'].search([
                        ('code', '=', 'SE')
                    ])[0]
            elif self.document_section == 'nfce_cfe':
                domain['document_type_id'] = [('code', 'in', ('59', '65'))]
                self.document_type_id = \
                    self.env['l10n_br_fiscal.document.type'].search([
                        ('code', '=', '59')
                    ])[0]
            elif self.document_section == 'cte':
                domain['document_type_id'] = [('code', '=', '57')]
                self.document_type_id = \
                    self.env['l10n_br_fiscal.document.type'].search([
                        ('code', '=', '57')
                    ])[0]

            return {'domain': domain}

    def _create_return(self):
        return_docs = self.env[self._name]
        for record in self:
            fsc_op = record.fiscal_operation_id.return_fiscal_operation_id
            if not fsc_op:
                raise ValidationError(
                    _("The fiscal operation {} has no return Fiscal "
                      "Operation defined".format(record.fiscal_operation_id)))

            new_doc = record.copy()
            new_doc.fiscal_operation_id = fsc_op
            new_doc._onchange_fiscal_operation_id()

            for l in new_doc.line_ids:
                fsc_op_line = l.fiscal_operation_id.return_fiscal_operation_id
                if not fsc_op_line:
                    raise ValidationError(
                        _("The fiscal operation {} has no return Fiscal "
                          "Operation defined".format(l.fiscal_operation_id)))
                l.fiscal_operation_id = fsc_op_line
                l._onchange_fiscal_operation_id()
                l._onchange_fiscal_operation_line_id()

            return_docs |= new_doc
        return return_docs

    @api.multi
    def action_create_return(self):
        action = self.env.ref('l10n_br_fiscal.document_all_action').read()[0]
        return_docs = self._create_return()

        if return_docs:
            action['domain'] = literal_eval(action['domain'] or '[]')
            action['domain'].append(('id', 'in', return_docs.ids))

        return action

    def _get_email_template(self, state):
        self.ensure_one()
        return self.document_type_id.document_email_ids.search(
            [
                '|', ('state_edoc', '=', False), ('state_edoc', '=', state),
                ('issuer', '=', self.issuer), '|',
                ('document_type_id', '=', False),
                ('document_type_id', '=', self.document_type_id.id)
            ],
            limit=1,
            order='state_edoc, document_type_id').mapped('email_template_id')

    def send_email(self, state):
        email_template = self._get_email_template(state)
        if email_template:
            email_template.send_mail(self.id)

    def _after_change_state(self, old_state, new_state):
        super()._after_change_state(old_state, new_state)
        self.send_email(new_state)

    def _exec_after_SITUACAO_EDOC_A_ENVIAR(self, old_state, new_state):
        super()._exec_after_SITUACAO_EDOC_A_ENVIAR(old_state, new_state)
        self.document_comment()

    @api.onchange('fiscal_operation_id')
    def _onchange_fiscal_operation_id(self):
        super()._onchange_fiscal_operation_id()
        if self.fiscal_operation_id:
            self.fiscal_operation_type = (
                self.fiscal_operation_id.fiscal_operation_type)
            self.ind_final = self.fiscal_operation_id.ind_final

        if self.issuer == DOCUMENT_ISSUER_COMPANY:
            self.document_type_id = self.company_id.document_type_id

        subsequent_documents = [(6, 0, {})]
        for subsequent_id in self.fiscal_operation_id.mapped(
                'operation_subsequent_ids'):
            subsequent_documents.append((0, 0, {
                'source_document_id':
                self.id,
                'subsequent_operation_id':
                subsequent_id.id,
                'fiscal_operation_id':
                subsequent_id.subsequent_operation_id.id,
            }))
        self.document_subsequent_ids = subsequent_documents

    @api.onchange('document_type_id')
    def _onchange_document_type_id(self):
        if self.document_type_id and self.issuer == DOCUMENT_ISSUER_COMPANY:
            self.document_serie_id = self.document_type_id.get_document_serie(
                self.company_id, self.fiscal_operation_id)

    @api.onchange('document_serie_id')
    def _onchange_document_serie_id(self):
        if self.document_serie_id and self.issuer == DOCUMENT_ISSUER_COMPANY:
            self.document_serie = self.document_serie_id.code

    def _exec_after_SITUACAO_EDOC_AUTORIZADA(self, old_state, new_state):
        super(Document,
              self)._exec_after_SITUACAO_EDOC_AUTORIZADA(old_state, new_state)
        self._generates_subsequent_operations()

    def _prepare_referenced_subsequent(self):
        vals = {
            'fiscal_document_id': self.id,
            'partner_id': self.partner_id.id,
            'document_type_id': self.document_type,
            'serie': self.document_serie,
            'number': self.number,
            'date': self.date,
            'document_key': self.key,
        }
        reference_id = self.env['l10n_br_fiscal.document.related'].create(vals)
        return reference_id

    def _document_reference(self, reference_ids):
        for referenced_item in reference_ids:
            referenced_item.fiscal_document_related_ids = self.id
            self.fiscal_document_related_ids |= referenced_item

    @api.depends('document_subsequent_ids.subsequent_document_id')
    def _compute_document_subsequent_generated(self):
        for document in self:
            if not document.document_subsequent_ids:
                continue
            document.document_subsequent_generated = all(
                subsequent_id.operation_performed
                for subsequent_id in document.document_subsequent_ids)

    def _generates_subsequent_operations(self):
        for record in self.filtered(
                lambda doc: not doc.document_subsequent_generated):
            for subsequent_id in record.document_subsequent_ids.filtered(
                    lambda doc_sub: doc_sub._confirms_document_generation()):
                subsequent_id.generate_subsequent_document()

    def cancel_edoc(self):
        self.ensure_one()
        if any(doc.state_edoc == SITUACAO_EDOC_AUTORIZADA for doc in
               self.document_subsequent_ids.mapped('document_subsequent_ids')):
            message = _("Canceling the document is not allowed: one or more "
                        "associated documents have already been authorized.")
            raise UserWarning(message)

    @api.multi
    def action_send_email(self):
        """ Open a window to compose an email, with the fiscal document_type
        template message loaded by default
        """
        self.ensure_one()
        template = self._get_email_template(self.state)
        compose_form = self.env.ref('mail.email_compose_message_wizard_form',
                                    False)
        lang = self.env.context.get('lang')
        if template and template.lang:
            lang = template._render_template(template.lang, self._name,
                                             self.id)
        self = self.with_context(lang=lang)
        ctx = dict(default_model='l10n_br_fiscal.document',
                   default_res_id=self.id,
                   default_use_template=bool(template),
                   default_template_id=template and template.id or False,
                   default_composition_mode='comment',
                   model_description=self.document_type_id.name or self._name,
                   force_email=True)
        return {
            'name': _('Send Fiscal Document Email Notification'),
            '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,
        }
class excise_move(models.Model):
    _name = 'excise.move'
    _description = 'Excise Line'

    name = fields.Text('Description', index=True, required=True)
    stock_move_id = fields.Many2one('stock.move',
                                    'Stock Move',
                                    check_company=True,
                                    index=True)
    stock_move_line_id = fields.Many2one('stock.move.line',
                                         'Stock Move Line',
                                         check_company=True,
                                         index=True)
    date = fields.Datetime('Date',
                           default=fields.Datetime.now,
                           index=True,
                           required=True,
                           readonly=True,
                           related='stock_move_id.date',
                           store=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True,
                                 index=True)
    currency_id = fields.Many2one('res.currency',
                                  string="Currency",
                                  readonly=True)
    product_id = fields.Many2one(
        'product.product',
        'Product',
        check_company=True,
        domain=
        "[('type', '!=', 'service'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]"
    )
    move_qty = fields.Float('Movement Quantity',
                            default=0.0,
                            digits='Product Unit of Measure',
                            copy=False)
    move_state = fields.Selection(
        [('draft', 'New'), ('cancel', 'Cancelled'),
         ('waiting', 'Waiting Another Move'),
         ('confirmed', 'Waiting Availability'),
         ('partially_available', 'Partially Available'),
         ('assigned', 'Available'), ('done', 'Done')],
        string='Status',
        copy=False,
        default='draft',
        index=True,
        readonly=True,
        related='stock_move_id.state',
        store=True)
    move_reference = fields.Char(related='stock_move_id.reference',
                                 string="Reference",
                                 store=True)
    move_location_id = fields.Many2one('stock.location',
                                       'Source Location',
                                       related='stock_move_id.location_id',
                                       readonly=True)
    move_location_dest_id = fields.Many2one(
        'stock.location',
        'Destination Location',
        related='stock_move_id.location_dest_id',
        readonly=True)
    move_partner_id = fields.Many2one('res.partner',
                                      'Destination Address ',
                                      related='stock_move_id.partner_id',
                                      readonly=True)

    excise_abv = fields.Float('ABV',
                              help='Average By Volume (% Alcohol)',
                              readonly=True)
    excise_move_volume = fields.Float(
        'Excisable Volume (L)',
        help='Volume being moved for the basis of the Excise calculation')
    excise_alcohol = fields.Float('Volume of alcohol (L)', readonly=True)
    excise_category = fields.Many2one('excise.category',
                                      'Excise Category',
                                      readonly=True)
    excise_rate = fields.Monetary('Rate', readonly=True)
    excise_amount_tax = fields.Monetary(string='Excise Amount', readonly=True)
    excise_payable = fields.Monetary(
        string='Total Excise Amount.',
        help=
        'Total excise payable after releifs (e.g. samll brewers allowance)',
        readonly=True)
class StockMoveLine(models.Model):
    _inherit = 'stock.move.line'

    sale_line = fields.Many2one(
        related='move_id.sale_line_id', readonly=True,
        string='Related order line',
        related_sudo=True,  # See explanation for sudo in compute method
    )
    currency_id = fields.Many2one(
        related='sale_line.currency_id', readonly=True,
        string='Sale Currency',
        related_sudo=True,
    )
    sale_tax_id = fields.Many2many(
        related='sale_line.tax_id', readonly=True,
        string='Sale Tax',
        related_sudo=True,
    )
    sale_price_unit = fields.Float(
        related='sale_line.price_unit', readonly=True,
        string='Sale price unit',
        related_sudo=True,
    )
    sale_discount = fields.Float(
        related='sale_line.discount', readonly=True,
        string='Sale discount (%)',
        related_sudo=True,
    )
    sale_tax_description = fields.Char(
        compute='_compute_sale_order_line_fields',
        string='Tax Description',
        compute_sudo=True,  # See explanation for sudo in compute method
    )
    sale_price_subtotal = fields.Monetary(
        compute='_compute_sale_order_line_fields',
        string='Price subtotal',
        compute_sudo=True,
    )
    sale_price_tax = fields.Float(
        compute='_compute_sale_order_line_fields',
        string='Taxes',
        compute_sudo=True,
    )
    sale_price_total = fields.Monetary(
        compute='_compute_sale_order_line_fields',
        string='Total',
        compute_sudo=True,
    )

    @api.multi
    def _compute_sale_order_line_fields(self):
        """This is computed with sudo for avoiding problems if you don't have
        access to sales orders (stricter warehouse users, inter-company
        records...).
        """
        for line in self:
            taxes = line.sale_tax_id.compute_all(
                price_unit=line.sale_line.price_reduce,
                currency=line.currency_id,
                quantity=line.qty_done or line.product_qty,
                product=line.product_id,
                partner=line.sale_line.order_id.partner_shipping_id)
            if line.sale_line.company_id.tax_calculation_rounding_method == (
                    'round_globally'):
                price_tax = sum(
                    t.get('amount', 0.0) for t in taxes.get('taxes', []))
            else:
                price_tax = taxes['total_included'] - taxes['total_excluded']
            line.update({
                'sale_tax_description': ', '.join(
                    t.name or t.description for t in line.sale_tax_id),
                'sale_price_subtotal': taxes['total_excluded'],
                'sale_price_tax': price_tax,
                'sale_price_total': taxes['total_included'],
            })
Exemple #25
0
class HrExpenseSheet(models.Model):
    _inherit = "hr.expense.sheet"

    advance = fields.Boolean(string="Employee Advance",
                             compute="_compute_advance",
                             store=True)
    advance_sheet_id = fields.Many2one(
        comodel_name="hr.expense.sheet",
        string="Clear Advance",
        domain="[('advance', '=', True), ('employee_id', '=', employee_id),"
        " ('clearing_residual', '>', 0.0)]",
        readonly=True,
        states={
            "draft": [("readonly", False)],
            "submit": [("readonly", False)],
            "approve": [("readonly", False)],
        },
        help="Show remaining advance of this employee",
    )
    clearing_residual = fields.Monetary(
        string="Amount to clear",
        compute="_compute_clearing_residual",
        store=True,
        help="Amount to clear of this expense sheet in company currency",
    )
    advance_sheet_residual = fields.Monetary(
        string="Advance Remaining",
        related="advance_sheet_id.clearing_residual",
        store=True,
        help="Remaining amount to clear the selected advance sheet",
    )
    amount_payable = fields.Monetary(
        string="Payable Amount",
        compute="_compute_amount_payable",
        help="Final regiter payment amount even after advance clearing",
    )

    @api.depends("expense_line_ids")
    def _compute_advance(self):
        for sheet in self:
            sheet.advance = bool(
                sheet.expense_line_ids.filtered("advance")
                and len(sheet.expense_line_ids) == 1)
        return

    @api.constrains("advance_sheet_id", "expense_line_ids")
    def _check_advance_expense(self):
        advance_lines = self.expense_line_ids.filtered("advance")
        if self.advance_sheet_id and advance_lines:
            raise ValidationError(
                _("Advance clearing must not contain any advance expense line")
            )
        if advance_lines and len(self.expense_line_ids) != 1:
            raise ValidationError(
                _("Advance must contain only 1 advance expense line"))

    @api.depends("account_move_id.line_ids.amount_residual")
    def _compute_clearing_residual(self):
        emp_advance = self.env.ref(
            "hr_expense_advance_clearing.product_emp_advance", False)
        for sheet in self:
            residual_company = 0.0
            if emp_advance:
                for line in sheet.sudo().account_move_id.line_ids:
                    if line.account_id == emp_advance.property_account_expense_id:
                        residual_company += line.amount_residual
            sheet.clearing_residual = residual_company

    def _compute_amount_payable(self):
        for sheet in self:
            rec_lines = sheet.account_move_id.line_ids.filtered(
                lambda x: x.credit and x.account_id.reconcile and not x.
                reconciled)
            sheet.amount_payable = -sum(rec_lines.mapped("amount_residual"))

    def action_sheet_move_create(self):
        res = super(HrExpenseSheet, self).action_sheet_move_create()
        # Reconcile advance of this sheet with the advance_sheet
        emp_advance = self.env.ref(
            "hr_expense_advance_clearing.product_emp_advance")
        for sheet in self:
            move_lines = (sheet.account_move_id.line_ids
                          | sheet.advance_sheet_id.account_move_id.line_ids)
            account_id = emp_advance.property_account_expense_id.id
            adv_move_lines = (self.env["account.move.line"].sudo().search([
                ("id", "in", move_lines.ids), ("account_id", "=", account_id)
            ]))
            adv_move_lines.reconcile()
        return res

    def open_clear_advance(self):
        self.ensure_one()
        action = self.env.ref("hr_expense_advance_clearing."
                              "action_hr_expense_sheet_advance_clearing")
        vals = action.read()[0]
        context1 = vals.get("context", {})
        if context1:
            context1 = safe_eval(context1)
        context1["default_advance_sheet_id"] = self.id
        vals["context"] = context1
        return vals
Exemple #26
0
class ProductWishlist(models.Model):
    _name = 'product.wishlist'
    _description = 'Product Wishlist'
    _sql_constraints = [
        ("product_unique_partner_id",
         "UNIQUE(product_id, partner_id)",
         "Duplicated wishlisted product for this partner."),
    ]

    partner_id = fields.Many2one('res.partner', string='Owner')
    product_id = fields.Many2one('product.product', string='Product', required=True)
    currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', readonly=True)
    pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', help='Pricelist when added')
    price = fields.Monetary(currency_field='currency_id', string='Price', help='Price of the product when it has been added in the wishlist')
    website_id = fields.Many2one('website', ondelete='cascade', required=True)
    active = fields.Boolean(default=True, required=True)

    @api.model
    def current(self):
        """Get all wishlist items that belong to current user or session,
        filter products that are unpublished."""
        if not request:
            return self

        if request.website.is_public_user():
            wish = self.sudo().search([('id', 'in', request.session.get('wishlist_ids', []))])
        else:
            wish = self.search([("partner_id", "=", self.env.user.partner_id.id)])

        return wish.sudo().filtered('product_id.product_tmpl_id.website_published')

    @api.model
    def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False):
        wish = self.env['product.wishlist'].create({
            'partner_id': partner_id,
            'product_id': product_id,
            'currency_id': currency_id,
            'pricelist_id': pricelist_id,
            'price': price,
            'website_id': website_id,
        })
        return wish

    @api.model
    def _check_wishlist_from_session(self):
        """Assign all wishlist withtout partner from this the current session"""
        session_wishes = self.sudo().search([('id', 'in', request.session.get('wishlist_ids', []))])
        partner_wishes = self.sudo().search([("partner_id", "=", self.env.user.partner_id.id)])
        partner_products = partner_wishes.mapped("product_id")
        # Remove session products already present for the user
        duplicated_wishes = session_wishes.filtered(lambda wish: wish.product_id <= partner_products)
        session_wishes -= duplicated_wishes
        duplicated_wishes.unlink()
        # Assign the rest to the user
        session_wishes.write({"partner_id": self.env.user.partner_id.id})
        request.session.pop('wishlist_ids')

    @api.model
    def _garbage_collector(self, *args, **kwargs):
        """Remove wishlists for unexisting sessions."""
        self.with_context(active_test=False).search([
            ("create_date", "<", fields.Datetime.to_string(datetime.now() - timedelta(weeks=kwargs.get('wishlist_week', 5)))),
            ("partner_id", "=", False),
        ]).unlink()
class SaleAdvancePaymentInv(models.TransientModel):
    _name = "sale.advance.payment.inv"
    _description = "Sales Advance Payment Invoice"

    @api.model
    def _count(self):
        return len(self._context.get('active_ids', []))

    @api.model
    def _default_product_id(self):
        product_id = self.env['ir.config_parameter'].sudo().get_param('sale.default_deposit_product_id')
        return self.env['product.product'].browse(int(product_id)).exists()

    @api.model
    def _default_deposit_account_id(self):
        return self._default_product_id()._get_product_accounts()['income']

    @api.model
    def _default_deposit_taxes_id(self):
        return self._default_product_id().taxes_id

    @api.model
    def _default_has_down_payment(self):
        if self._context.get('active_model') == 'sale.order' and self._context.get('active_id', False):
            sale_order = self.env['sale.order'].browse(self._context.get('active_id'))
            return sale_order.order_line.filtered(
                lambda sale_order_line: sale_order_line.is_downpayment
            )

        return False

    @api.model
    def _default_currency_id(self):
        if self._context.get('active_model') == 'sale.order' and self._context.get('active_id', False):
            sale_order = self.env['sale.order'].browse(self._context.get('active_id'))
            return sale_order.currency_id

    advance_payment_method = fields.Selection([
        ('delivered', 'Regular invoice'),
        ('percentage', 'Down payment (percentage)'),
        ('fixed', 'Down payment (fixed amount)')
        ], string='Create Invoice', default='delivered', required=True,
        help="A standard invoice is issued with all the order lines ready for invoicing, \
        according to their invoicing policy (based on ordered or delivered quantity).")
    deduct_down_payments = fields.Boolean('Deduct down payments', default=True)
    has_down_payments = fields.Boolean('Has down payments', default=_default_has_down_payment, readonly=True)
    product_id = fields.Many2one('product.product', string='Down Payment Product', domain=[('type', '=', 'service')],
        default=_default_product_id)
    count = fields.Integer(default=_count, string='Order Count')
    amount = fields.Float('Down Payment Amount', digits='Account', help="The percentage of amount to be invoiced in advance, taxes excluded.")
    currency_id = fields.Many2one('res.currency', string='Currency', default=_default_currency_id)
    fixed_amount = fields.Monetary('Down Payment Amount (Fixed)', help="The fixed amount to be invoiced in advance, taxes excluded.")
    deposit_account_id = fields.Many2one("account.account", string="Income Account", domain=[('deprecated', '=', False)],
        help="Account used for deposits", default=_default_deposit_account_id)
    deposit_taxes_id = fields.Many2many("account.tax", string="Customer Taxes", help="Taxes used for deposits", default=_default_deposit_taxes_id)

    @api.onchange('advance_payment_method')
    def onchange_advance_payment_method(self):
        if self.advance_payment_method == 'percentage':
            amount = self.default_get(['amount']).get('amount')
            return {'value': {'amount': amount}}
        return {}

    def _prepare_invoice_values(self, order, name, amount, so_line):
        invoice_vals = {
            'ref': order.client_order_ref,
            'move_type': 'out_invoice',
            'invoice_origin': order.name,
            'invoice_user_id': order.user_id.id,
            'narration': order.note,
            'partner_id': order.partner_invoice_id.id,
            'fiscal_position_id': (order.fiscal_position_id or order.fiscal_position_id.get_fiscal_position(order.partner_id.id)).id,
            'partner_shipping_id': order.partner_shipping_id.id,
            'currency_id': order.pricelist_id.currency_id.id,
            'payment_reference': order.reference,
            'invoice_payment_term_id': order.payment_term_id.id,
            'partner_bank_id': order.company_id.partner_id.bank_ids[:1].id,
            'team_id': order.team_id.id,
            'campaign_id': order.campaign_id.id,
            'medium_id': order.medium_id.id,
            'source_id': order.source_id.id,
            'invoice_line_ids': [(0, 0, {
                'name': name,
                'price_unit': amount,
                'quantity': 1.0,
                'product_id': self.product_id.id,
                'product_uom_id': so_line.product_uom.id,
                'tax_ids': [(6, 0, so_line.tax_id.ids)],
                'sale_line_ids': [(6, 0, [so_line.id])],
                'analytic_tag_ids': [(6, 0, so_line.analytic_tag_ids.ids)],
                'analytic_account_id': order.analytic_account_id.id or False,
            })],
        }

        return invoice_vals

    def _get_advance_details(self, order):
        if self.advance_payment_method == 'percentage':
            amount = order.amount_untaxed * self.amount / 100
            name = _("Down payment of %s%%") % (self.amount)
        else:
            amount = self.fixed_amount
            name = _('Down Payment')

        return amount, name

    def _create_invoice(self, order, so_line, amount):
        if (self.advance_payment_method == 'percentage' and self.amount <= 0.00) or (self.advance_payment_method == 'fixed' and self.fixed_amount <= 0.00):
            raise UserError(_('The value of the down payment amount must be positive.'))

        amount, name = self._get_advance_details(order)

        invoice_vals = self._prepare_invoice_values(order, name, amount, so_line)

        if order.fiscal_position_id:
            invoice_vals['fiscal_position_id'] = order.fiscal_position_id.id
        invoice = self.env['account.move'].sudo().create(invoice_vals).with_user(self.env.uid)
        invoice.message_post_with_view('mail.message_origin_link',
                    values={'self': invoice, 'origin': order},
                    subtype_id=self.env.ref('mail.mt_note').id)
        return invoice

    def _prepare_so_line(self, order, analytic_tag_ids, tax_ids, amount):
        so_values = {
            'name': _('Down Payment: %s') % (time.strftime('%m %Y'),),
            'price_unit': amount,
            'product_uom_qty': 0.0,
            'order_id': order.id,
            'discount': 0.0,
            'product_uom': self.product_id.uom_id.id,
            'product_id': self.product_id.id,
            'analytic_tag_ids': analytic_tag_ids,
            'tax_id': [(6, 0, tax_ids)],
            'is_downpayment': True,
            'sequence': order.order_line and order.order_line[-1].sequence + 1 or 10,
        }
        return so_values

    def create_invoices(self):
        sale_orders = self.env['sale.order'].browse(self._context.get('active_ids', []))

        if self.advance_payment_method == 'delivered':
            sale_orders._create_invoices(final=self.deduct_down_payments)
        else:
            # Create deposit product if necessary
            if not self.product_id:
                vals = self._prepare_deposit_product()
                self.product_id = self.env['product.product'].create(vals)
                self.env['ir.config_parameter'].sudo().set_param('sale.default_deposit_product_id', self.product_id.id)

            sale_line_obj = self.env['sale.order.line']
            for order in sale_orders:
                amount, name = self._get_advance_details(order)

                if self.product_id.invoice_policy != 'order':
                    raise UserError(_('The product used to invoice a down payment should have an invoice policy set to "Ordered quantities". Please update your deposit product to be able to create a deposit invoice.'))
                if self.product_id.type != 'service':
                    raise UserError(_("The product used to invoice a down payment should be of type 'Service'. Please use another product or update this product."))
                taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id)
                tax_ids = order.fiscal_position_id.map_tax(taxes, self.product_id, order.partner_shipping_id).ids
                context = {'lang': order.partner_id.lang}
                analytic_tag_ids = []
                for line in order.order_line:
                    analytic_tag_ids = [(4, analytic_tag.id, None) for analytic_tag in line.analytic_tag_ids]

                so_line_values = self._prepare_so_line(order, analytic_tag_ids, tax_ids, amount)
                so_line = sale_line_obj.create(so_line_values)
                del context
                self._create_invoice(order, so_line, amount)
        if self._context.get('open_invoices', False):
            return sale_orders.action_view_invoice()
        return {'type': 'ir.actions.act_window_close'}

    def _prepare_deposit_product(self):
        return {
            'name': 'Down payment',
            'type': 'service',
            'invoice_policy': 'order',
            'property_account_income_id': self.deposit_account_id.id,
            'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
            'company_id': False,
        }
class InvoiceEletronic(models.Model):
    _inherit = 'invoice.eletronic'

    @api.multi
    @api.depends('chave_nfe')
    def _format_danfe_key(self):
        for item in self:
            item.chave_nfe_danfe = re.sub("(.{4})", "\\1.", item.chave_nfe, 10,
                                          re.DOTALL)

    @api.multi
    def generate_correction_letter(self):
        return {
            "type": "ir.actions.act_window",
            "res_model": "wizard.carta.correcao.eletronica",
            "views": [[False, "form"]],
            "name": "Carta de Correção",
            "target": "new",
            "context": {
                'default_eletronic_doc_id': self.id
            },
        }

    payment_mode_id = fields.Many2one('payment.mode',
                                      string='Modo de Pagamento',
                                      readonly=True,
                                      states=STATE)
    state = fields.Selection(selection_add=[('denied', 'Denegado')])
    ambiente_nfe = fields.Selection(string=u"Ambiente NFe",
                                    related="company_id.tipo_ambiente",
                                    readonly=True)
    ind_final = fields.Selection([('0', u'Não'), ('1', u'Sim')],
                                 u'Consumidor Final',
                                 readonly=True,
                                 states=STATE,
                                 required=False,
                                 help=u'Indica operação com Consumidor final.',
                                 default='0')
    ind_pres = fields.Selection([
        ('0', u'Não se aplica'),
        ('1', u'Operação presencial'),
        ('2', u'Operação não presencial, pela Internet'),
        ('3', u'Operação não presencial, Teleatendimento'),
        ('4', u'NFC-e em operação com entrega em domicílio'),
        ('5', u'Operação presencial, fora do estabelecimento'),
        ('9', u'Operação não presencial, outros'),
    ],
                                u'Indicador de Presença',
                                readonly=True,
                                states=STATE,
                                required=False,
                                help=u'Indicador de presença do comprador no\n'
                                u'estabelecimento comercial no momento\n'
                                u'da operação.',
                                default='0')
    ind_dest = fields.Selection([('1', u'1 - Operação Interna'),
                                 ('2', u'2 - Operação Interestadual'),
                                 ('3', u'3 - Operação com exterior')],
                                string=u"Indicador Destinatário",
                                readonly=True,
                                states=STATE)
    ind_ie_dest = fields.Selection(
        [('1', u'1 - Contribuinte ICMS'),
         ('2', u'2 - Contribuinte Isento de Cadastro'),
         ('9', u'9 - Não Contribuinte')],
        string=u"Indicador IE Dest.",
        help=u"Indicador da IE do desinatário",
        readonly=True,
        states=STATE)
    tipo_emissao = fields.Selection(
        [('1', u'1 - Emissão normal'),
         ('2', u'2 - Contingência FS-IA, com impressão do DANFE em formulário \
         de segurança'), ('3', u'3 - Contingência SCAN'),
         ('4', u'4 - Contingência DPEC'),
         ('5', u'5 - Contingência FS-DA, com impressão do DANFE em \
         formulário de segurança'), ('6', u'6 - Contingência SVC-AN'),
         ('7', u'7 - Contingência SVC-RS'),
         ('9', u'9 - Contingência off-line da NFC-e')],
        string=u"Tipo de Emissão",
        readonly=True,
        states=STATE,
        default='1')

    # Transporte
    modalidade_frete = fields.Selection(
        [('0', '0 - Contratação do Frete por conta do Remetente (CIF)'),
         ('1', '1 - Contratação do Frete por conta do Destinatário (FOB)'),
         ('2', '2 - Contratação do Frete por conta de Terceiros'),
         ('3', '3 - Transporte Próprio por conta do Remetente'),
         ('4', '4 - Transporte Próprio por conta do Destinatário'),
         ('9', '9 - Sem Ocorrência de Transporte')],
        string=u'Modalidade do frete',
        default="9",
        readonly=True,
        states=STATE)
    transportadora_id = fields.Many2one('res.partner',
                                        string=u"Transportadora",
                                        readonly=True,
                                        states=STATE)
    placa_veiculo = fields.Char(string=u'Placa do Veículo',
                                size=7,
                                readonly=True,
                                states=STATE)
    uf_veiculo = fields.Char(string=u'UF da Placa',
                             size=2,
                             readonly=True,
                             states=STATE)
    rntc = fields.Char(string="RNTC",
                       size=20,
                       readonly=True,
                       states=STATE,
                       help=u"Registro Nacional de Transportador de Carga")

    reboque_ids = fields.One2many('nfe.reboque',
                                  'invoice_eletronic_id',
                                  string=u"Reboques",
                                  readonly=True,
                                  states=STATE)
    volume_ids = fields.One2many('nfe.volume',
                                 'invoice_eletronic_id',
                                 string=u"Volumes",
                                 readonly=True,
                                 states=STATE)

    # Exportação
    uf_saida_pais_id = fields.Many2one('res.country.state',
                                       domain=[('country_id.code', '=', 'BR')],
                                       string=u"UF Saída do País",
                                       readonly=True,
                                       states=STATE)
    local_embarque = fields.Char(string=u'Local de Embarque',
                                 size=60,
                                 readonly=True,
                                 states=STATE)
    local_despacho = fields.Char(string=u'Local de Despacho',
                                 size=60,
                                 readonly=True,
                                 states=STATE)

    # Cobrança
    numero_fatura = fields.Char(string=u"Fatura", readonly=True, states=STATE)
    fatura_bruto = fields.Monetary(string=u"Valor Original",
                                   readonly=True,
                                   states=STATE)
    fatura_desconto = fields.Monetary(string=u"Desconto",
                                      readonly=True,
                                      states=STATE)
    fatura_liquido = fields.Monetary(string=u"Valor Líquido",
                                     readonly=True,
                                     states=STATE)

    duplicata_ids = fields.One2many('nfe.duplicata',
                                    'invoice_eletronic_id',
                                    string=u"Duplicatas",
                                    readonly=True,
                                    states=STATE)

    # Compras
    nota_empenho = fields.Char(string="Nota de Empenho",
                               size=22,
                               readonly=True,
                               states=STATE)
    pedido_compra = fields.Char(string="Pedido Compra",
                                size=60,
                                readonly=True,
                                states=STATE)
    contrato_compra = fields.Char(string="Contrato Compra",
                                  size=60,
                                  readonly=True,
                                  states=STATE)

    sequencial_evento = fields.Integer(string=u"Sequêncial Evento",
                                       default=1,
                                       readonly=True,
                                       states=STATE)
    recibo_nfe = fields.Char(string=u"Recibo NFe",
                             size=50,
                             readonly=True,
                             states=STATE)
    chave_nfe = fields.Char(string=u"Chave NFe",
                            size=50,
                            readonly=True,
                            states=STATE)
    chave_nfe_danfe = fields.Char(string=u"Chave Formatado",
                                  compute="_format_danfe_key")
    protocolo_nfe = fields.Char(string=u"Protocolo",
                                size=50,
                                readonly=True,
                                states=STATE,
                                help=u"Protocolo de autorização da NFe")
    nfe_processada = fields.Binary(string=u"Xml da NFe", readonly=True)
    nfe_processada_name = fields.Char(string=u"Xml da NFe",
                                      size=100,
                                      readonly=True)

    valor_icms_uf_remet = fields.Monetary(
        string=u"ICMS Remetente",
        readonly=True,
        states=STATE,
        help=u'Valor total do ICMS Interestadual para a UF do Remetente')
    valor_icms_uf_dest = fields.Monetary(
        string=u"ICMS Destino",
        readonly=True,
        states=STATE,
        help=u'Valor total do ICMS Interestadual para a UF de destino')
    valor_icms_fcp_uf_dest = fields.Monetary(
        string=u"Total ICMS FCP",
        readonly=True,
        states=STATE,
        help=u'Total total do ICMS relativo Fundo de Combate à Pobreza (FCP) \
        da UF de destino')

    # Documentos Relacionados
    fiscal_document_related_ids = fields.One2many(
        'br_account.document.related',
        'invoice_eletronic_id',
        u'Documentos Fiscais Relacionados',
        readonly=True,
        states=STATE)

    # CARTA DE CORRECAO
    cartas_correcao_ids = fields.One2many('carta.correcao.eletronica.evento',
                                          'eletronic_doc_id',
                                          string=u"Cartas de Correção",
                                          readonly=True,
                                          states=STATE)

    def can_unlink(self):
        res = super(InvoiceEletronic, self).can_unlink()
        if self.state == 'denied':
            return False
        return res

    @api.multi
    def unlink(self):
        for item in self:
            if item.state in ('denied'):
                raise UserError(
                    u'Documento Eletrônico Denegado - Proibido excluir')
        super(InvoiceEletronic, self).unlink()

    @api.multi
    def _hook_validation(self):
        errors = super(InvoiceEletronic, self)._hook_validation()
        if self.model == '55':
            if not self.company_id.partner_id.inscr_est:
                errors.append(u'Emitente / Inscrição Estadual')
            if not self.fiscal_position_id:
                errors.append(u'Configure a posição fiscal')
            if self.company_id.accountant_id and not \
               self.company_id.accountant_id.cnpj_cpf:
                errors.append(u'Emitente / CNPJ do escritório contabilidade')

            for eletr in self.eletronic_item_ids:
                prod = u"Produto: %s - %s" % (eletr.product_id.default_code,
                                              eletr.product_id.name)
                if not eletr.cfop:
                    errors.append(u'%s - CFOP' % prod)
                if eletr.tipo_produto == 'product':
                    if not eletr.icms_cst:
                        errors.append(u'%s - CST do ICMS' % prod)
                    if not eletr.ipi_cst:
                        errors.append(u'%s - CST do IPI' % prod)
                if eletr.tipo_produto == 'service':
                    if not eletr.issqn_codigo:
                        errors.append(u'%s - Código de Serviço' % prod)
                if not eletr.pis_cst:
                    errors.append(u'%s - CST do PIS' % prod)
                if not eletr.cofins_cst:
                    errors.append(u'%s - CST do Cofins' % prod)

        return errors

    @api.multi
    def _prepare_eletronic_invoice_item(self, item, invoice):
        res = super(InvoiceEletronic,
                    self)._prepare_eletronic_invoice_item(item, invoice)
        if self.model not in ('55', '65'):
            return res

        if self.ambiente != 'homologacao':
            xProd = item.product_id.with_context(
                display_default_code=False).name_get()[0][1]
        else:
            xProd = 'NOTA FISCAL EMITIDA EM AMBIENTE DE HOMOLOGACAO -\
 SEM VALOR FISCAL'

        prod = {
            'cProd': item.product_id.default_code,
            'cEAN': item.product_id.barcode or 'SEM GTIN',
            'xProd': xProd,
            'NCM': re.sub('[^0-9]', '', item.ncm or '')[:8],
            'EXTIPI': re.sub('[^0-9]', '', item.ncm or '')[8:],
            'CFOP': item.cfop,
            'uCom': '{:.6}'.format(item.uom_id.name or ''),
            'qCom': item.quantidade,
            'vUnCom': "%.02f" % item.preco_unitario,
            'vProd': "%.02f" % (item.preco_unitario * item.quantidade),
            'cEANTrib': item.product_id.barcode or 'SEM GTIN',
            'uTrib': '{:.6}'.format(item.uom_id.name or ''),
            'qTrib': item.quantidade,
            'vUnTrib': "%.02f" % item.preco_unitario,
            'vFrete': "%.02f" % item.frete if item.frete else '',
            'vSeg': "%.02f" % item.seguro if item.seguro else '',
            'vDesc': "%.02f" % item.desconto if item.desconto else '',
            'vOutro':
            "%.02f" % item.outras_despesas if item.outras_despesas else '',
            'indTot': item.indicador_total,
            'cfop': item.cfop,
            'CEST': re.sub('[^0-9]', '', item.cest or ''),
            'nItemPed':
            item.item_pedido_compra if item.item_pedido_compra else '',
        }
        di_vals = []
        for di in item.import_declaration_ids:
            adicoes = []
            for adi in di.line_ids:
                adicoes.append({
                    'nAdicao':
                    adi.name,
                    'nSeqAdic':
                    adi.sequence,
                    'cFabricante':
                    adi.manufacturer_code,
                    'vDescDI':
                    "%.02f" %
                    adi.amount_discount if adi.amount_discount else '',
                    'nDraw':
                    adi.drawback_number or '',
                })

            dt_registration = datetime.strptime(di.date_registration,
                                                DATE_FORMAT)
            dt_release = datetime.strptime(di.date_release, DATE_FORMAT)
            di_vals.append({
                'nDI':
                di.name,
                'dDI':
                dt_registration.strftime('%Y-%m-%d'),
                'xLocDesemb':
                di.location,
                'UFDesemb':
                di.state_id.code,
                'dDesemb':
                dt_release.strftime('%Y-%m-%d'),
                'tpViaTransp':
                di.type_transportation,
                'vAFRMM':
                "%.02f" % di.afrmm_value if di.afrmm_value else '',
                'tpIntermedio':
                di.type_import,
                'CNPJ':
                di.thirdparty_cnpj or '',
                'UFTerceiro':
                di.thirdparty_state_id.code or '',
                'cExportador':
                di.exporting_code,
                'adi':
                adicoes,
            })

        prod["DI"] = di_vals

        imposto = {
            'vTotTrib': "%.02f" % item.tributos_estimados,
            'ICMS': {
                'orig': item.origem,
                'CST': item.icms_cst,
                'modBC': item.icms_tipo_base,
                'vBC': "%.02f" % item.icms_base_calculo,
                'pRedBC': "%.02f" % item.icms_aliquota_reducao_base,
                'pICMS': "%.02f" % item.icms_aliquota,
                'vICMS': "%.02f" % item.icms_valor,
                'modBCST': item.icms_st_tipo_base,
                'pMVAST': "%.02f" % item.icms_st_aliquota_mva,
                'pRedBCST': "%.02f" % item.icms_st_aliquota_reducao_base,
                'vBCST': "%.02f" % item.icms_st_base_calculo,
                'pICMSST': "%.02f" % item.icms_st_aliquota,
                'vICMSST': "%.02f" % item.icms_st_valor,
                'pCredSN': "%.02f" % item.icms_aliquota_credito,
                'vCredICMSSN': "%.02f" % item.icms_valor_credito
            },
            'IPI': {
                'clEnq': item.classe_enquadramento_ipi or '',
                'cEnq': item.codigo_enquadramento_ipi,
                'CST': item.ipi_cst,
                'vBC': "%.02f" % item.ipi_base_calculo,
                'pIPI': "%.02f" % item.ipi_aliquota,
                'vIPI': "%.02f" % item.ipi_valor
            },
            'PIS': {
                'CST': item.pis_cst,
                'vBC': "%.02f" % item.pis_base_calculo,
                'pPIS': "%.02f" % item.pis_aliquota,
                'vPIS': "%.02f" % item.pis_valor
            },
            'COFINS': {
                'CST': item.cofins_cst,
                'vBC': "%.02f" % item.cofins_base_calculo,
                'pCOFINS': "%.02f" % item.cofins_aliquota,
                'vCOFINS': "%.02f" % item.cofins_valor
            },
            'II': {
                'vBC': "%.02f" % item.ii_base_calculo,
                'vDespAdu': "%.02f" % item.ii_valor_despesas,
                'vII': "%.02f" % item.ii_valor,
                'vIOF': "%.02f" % item.ii_valor_iof
            },
        }
        if item.tem_difal:
            imposto['ICMSUFDest'] = {
                'vBCUFDest': "%.02f" % item.icms_bc_uf_dest,
                'pFCPUFDest': "%.02f" % item.icms_aliquota_fcp_uf_dest,
                'pICMSUFDest': "%.02f" % item.icms_aliquota_uf_dest,
                'pICMSInter': "%.02f" % item.icms_aliquota_interestadual,
                'pICMSInterPart': "%.02f" % item.icms_aliquota_inter_part,
                'vFCPUFDest': "%.02f" % item.icms_fcp_uf_dest,
                'vICMSUFDest': "%.02f" % item.icms_uf_dest,
                'vICMSUFRemet': "%.02f" % item.icms_uf_remet,
            }
        return {
            'prod': prod,
            'imposto': imposto,
            'infAdProd': item.informacao_adicional
        }

    @api.multi
    def _prepare_eletronic_invoice_values(self):
        res = super(InvoiceEletronic, self)._prepare_eletronic_invoice_values()
        if self.model not in ('55', '65'):
            return res

        dt_emissao = datetime.strptime(self.data_emissao, DTFT)

        ide = {
            'cUF':
            self.company_id.state_id.ibge_code,
            'cNF':
            "%08d" % self.numero_controle,
            'natOp':
            self.fiscal_position_id.name,
            'mod':
            self.model,
            'serie':
            self.serie.code,
            'nNF':
            self.numero,
            'dhEmi':
            dt_emissao.strftime('%Y-%m-%dT%H:%M:%S-00:00'),
            'dhSaiEnt':
            dt_emissao.strftime('%Y-%m-%dT%H:%M:%S-00:00'),
            'tpNF':
            '0' if self.tipo_operacao == 'entrada' else '1',
            'idDest':
            self.ind_dest or 1,
            'cMunFG':
            "%s%s" % (self.company_id.state_id.ibge_code,
                      self.company_id.city_id.ibge_code),
            # Formato de Impressão do DANFE - 1 - Danfe Retrato, 4 - Danfe NFCe
            'tpImp':
            '1' if self.model == '55' else '4',
            'tpEmis':
            int(self.tipo_emissao),
            'tpAmb':
            2 if self.ambiente == 'homologacao' else 1,
            'finNFe':
            self.finalidade_emissao,
            'indFinal':
            self.ind_final or '1',
            'indPres':
            self.ind_pres or '1',
            'procEmi':
            0
        }
        # Documentos Relacionados
        documentos = []
        for doc in self.fiscal_document_related_ids:
            data = fields.Datetime.from_string(doc.date)
            if doc.document_type == 'nfe':
                documentos.append({'refNFe': doc.access_key})
            elif doc.document_type == 'nf':
                documentos.append({
                    'refNF': {
                        'cUF': doc.state_id.ibge_code,
                        'AAMM': data.strftime("%y%m"),
                        'CNPJ': re.sub('[^0-9]', '', doc.cnpj_cpf),
                        'mod': doc.fiscal_document_id.code,
                        'serie': doc.serie,
                        'nNF': doc.internal_number,
                    }
                })

            elif doc.document_type == 'cte':
                documentos.append({'refCTe': doc.access_key})
            elif doc.document_type == 'nfrural':
                cnpj_cpf = re.sub('[^0-9]', '', doc.cnpj_cpf)
                documentos.append({
                    'refNFP': {
                        'cUF': doc.state_id.ibge_code,
                        'AAMM': data.strftime("%y%m"),
                        'CNPJ': cnpj_cpf if len(cnpj_cpf) == 14 else '',
                        'CPF': cnpj_cpf if len(cnpj_cpf) == 11 else '',
                        'IE': doc.inscr_est,
                        'mod': doc.fiscal_document_id.code,
                        'serie': doc.serie,
                        'nNF': doc.internal_number,
                    }
                })
            elif doc.document_type == 'cf':
                documentos.append({
                    'refECF': {
                        'mod': doc.fiscal_document_id.code,
                        'nECF': doc.serie,
                        'nCOO': doc.internal_number,
                    }
                })

        ide['NFref'] = documentos
        emit = {
            'tipo': self.company_id.partner_id.company_type,
            'cnpj_cpf': re.sub('[^0-9]', '', self.company_id.cnpj_cpf),
            'xNome': self.company_id.legal_name,
            'xFant': self.company_id.name,
            'enderEmit': {
                'xLgr':
                self.company_id.street,
                'nro':
                self.company_id.number,
                'xBairro':
                self.company_id.district,
                'cMun':
                '%s%s' % (self.company_id.partner_id.state_id.ibge_code,
                          self.company_id.partner_id.city_id.ibge_code),
                'xMun':
                self.company_id.city_id.name,
                'UF':
                self.company_id.state_id.code,
                'CEP':
                re.sub('[^0-9]', '', self.company_id.zip),
                'cPais':
                self.company_id.country_id.ibge_code,
                'xPais':
                self.company_id.country_id.name,
                'fone':
                re.sub('[^0-9]', '', self.company_id.phone or '')
            },
            'IE': re.sub('[^0-9]', '', self.company_id.inscr_est),
            'CRT': self.company_id.fiscal_type,
        }
        if self.company_id.cnae_main_id and self.company_id.inscr_mun:
            emit['IM'] = re.sub('[^0-9]', '', self.company_id.inscr_mun or '')
            emit['CNAE'] = re.sub('[^0-9]', '',
                                  self.company_id.cnae_main_id.code or '')
        dest = None
        exporta = None
        if self.commercial_partner_id:
            partner = self.commercial_partner_id
            dest = {
                'tipo': partner.company_type,
                'cnpj_cpf': re.sub('[^0-9]', '', partner.cnpj_cpf or ''),
                'xNome': partner.legal_name or partner.name,
                'enderDest': {
                    'xLgr':
                    partner.street,
                    'nro':
                    partner.number,
                    'xBairro':
                    partner.district,
                    'cMun':
                    '%s%s' %
                    (partner.state_id.ibge_code, partner.city_id.ibge_code),
                    'xMun':
                    partner.city_id.name,
                    'UF':
                    partner.state_id.code,
                    'CEP':
                    re.sub('[^0-9]', '', partner.zip or ''),
                    'cPais': (partner.country_id.bc_code or '')[-4:],
                    'xPais':
                    partner.country_id.name,
                    'fone':
                    re.sub('[^0-9]', '', partner.phone or '')
                },
                'indIEDest': self.ind_ie_dest,
                'IE': re.sub('[^0-9]', '', partner.inscr_est or ''),
            }
            if self.model == '65':
                dest.update(
                    {'CPF': re.sub('[^0-9]', '', partner.cnpj_cpf or '')})

            if self.ambiente == 'homologacao':
                dest['xNome'] = \
                    u'NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO -\
 SEM VALOR FISCAL'

            _logger.info(
                "==============before===idEstrangeiro=====================")
            if partner.country_id.id != self.company_id.country_id.id:
                dest['idEstrangeiro'] = re.sub('[^0-9]', '', partner.cnpj_cpf
                                               or '')
                _logger.info(
                    "=================idEstrangeiro=====================%s",
                    dest['idEstrangeiro'])
                dest['enderDest']['UF'] = 'EX'
                dest['enderDest']['xMun'] = 'Exterior'
                dest['enderDest']['cMun'] = '9999999'
                exporta = {
                    'UFSaidaPais': self.uf_saida_pais_id.code or '',
                    'xLocExporta': self.local_embarque or '',
                    'xLocDespacho': self.local_despacho or '',
                }

        autorizados = []
        if self.company_id.accountant_id:
            autorizados.append({
                'CNPJ':
                re.sub('[^0-9]', '', self.company_id.accountant_id.cnpj_cpf)
            })

        eletronic_items = []
        for item in self.eletronic_item_ids:
            eletronic_items.append(
                self._prepare_eletronic_invoice_item(item, self))
        total = {
            # ICMS
            'vBC': "%.02f" % self.valor_bc_icms,
            'vICMS': "%.02f" % self.valor_icms,
            'vICMSDeson': '0.00',
            'vFCP': '0.00',  # TODO Implementar aqui
            'vBCST': "%.02f" % self.valor_bc_icmsst,
            'vST': "%.02f" % self.valor_icmsst,
            'vFCPST': '0.00',
            'vFCPSTRet': '0.00',
            'vProd': "%.02f" % self.valor_bruto,
            'vFrete': "%.02f" % self.valor_frete,
            'vSeg': "%.02f" % self.valor_seguro,
            'vDesc': "%.02f" % self.valor_desconto,
            'vII': "%.02f" % self.valor_ii,
            'vIPI': "%.02f" % self.valor_ipi,
            'vIPIDevol': '0.00',
            'vPIS': "%.02f" % self.valor_pis,
            'vCOFINS': "%.02f" % self.valor_cofins,
            'vOutro': "%.02f" % self.valor_despesas,
            'vNF': "%.02f" % self.valor_final,
            'vFCPUFDest': "%.02f" % self.valor_icms_fcp_uf_dest,
            'vICMSUFDest': "%.02f" % self.valor_icms_uf_dest,
            'vICMSUFRemet': "%.02f" % self.valor_icms_uf_remet,
            'vTotTrib': "%.02f" % self.valor_estimado_tributos,
            # ISSQn
            'vServ': '0.00',
            # Retenções
        }
        if self.transportadora_id.street:
            end_transp = "%s - %s, %s" % (
                self.transportadora_id.street, self.transportadora_id.number
                or '', self.transportadora_id.district or '')
        else:
            end_transp = ''
        transp = {
            'modFrete': self.modalidade_frete,
            'transporta': {
                'xNome':
                self.transportadora_id.legal_name
                or self.transportadora_id.name or '',
                'IE':
                re.sub('[^0-9]', '', self.transportadora_id.inscr_est or ''),
                'xEnder':
                end_transp if self.transportadora_id else '',
                'xMun':
                self.transportadora_id.city_id.name or '',
                'UF':
                self.transportadora_id.state_id.code or ''
            },
            'veicTransp': {
                'placa': self.placa_veiculo or '',
                'UF': self.uf_veiculo or '',
                'RNTC': self.rntc or '',
            }
        }
        cnpj_cpf = re.sub('[^0-9]', '', self.transportadora_id.cnpj_cpf or '')
        if self.transportadora_id.is_company:
            transp['transporta']['CNPJ'] = cnpj_cpf
        else:
            transp['transporta']['CPF'] = cnpj_cpf

        reboques = []
        for item in self.reboque_ids:
            reboques.append({
                'placa': item.placa_veiculo or '',
                'UF': item.uf_veiculo or '',
                'RNTC': item.rntc or '',
                'vagao': item.vagao or '',
                'balsa': item.balsa or '',
            })
        transp['reboque'] = reboques
        volumes = []
        for item in self.volume_ids:
            volumes.append({
                'qVol':
                item.quantidade_volumes or '',
                'esp':
                item.especie or '',
                'marca':
                item.marca or '',
                'nVol':
                item.numeracao or '',
                'pesoL':
                "%.03f" % item.peso_liquido if item.peso_liquido else '',
                'pesoB':
                "%.03f" % item.peso_bruto if item.peso_bruto else '',
            })
        transp['vol'] = volumes

        duplicatas = []
        for dup in self.duplicata_ids:
            vencimento = fields.Datetime.from_string(dup.data_vencimento)
            duplicatas.append({
                'nDup': dup.numero_duplicata,
                'dVenc': vencimento.strftime('%Y-%m-%d'),
                'vDup': "%.02f" % dup.valor
            })
        cobr = {
            'fat': {
                'nFat': self.numero_fatura or '',
                'vOrig':
                "%.02f" % (self.fatura_liquido + self.fatura_desconto),
                'vDesc': "%.02f" % self.fatura_desconto,
                'vLiq': "%.02f" % self.fatura_liquido,
            },
            'dup': duplicatas
        }
        pag = [{
            'indPag': self.payment_term_id.indPag or '0',
            'tPag': self.payment_mode_id.tipo_pagamento or '99',
            'vPag': "%.02f" % self.valor_final
        }]
        self.informacoes_complementares = self.informacoes_complementares.\
            replace('\n', '<br />')
        self.informacoes_legais = self.informacoes_legais.replace(
            '\n', '<br />')
        infAdic = {
            'infCpl': self.informacoes_complementares or '',
            'infAdFisco': self.informacoes_legais or '',
        }
        compras = {
            'xNEmp': self.nota_empenho or '',
            'xPed': self.pedido_compra or '',
            'xCont': self.contrato_compra or '',
        }
        vals = {
            'Id': '',
            'ide': ide,
            'emit': emit,
            'dest': dest,
            'autXML': autorizados,
            'detalhes': eletronic_items,
            'total': total,
            'pag': pag,
            'transp': transp,
            'infAdic': infAdic,
            'exporta': exporta,
            'compra': compras,
        }
        if len(duplicatas) > 0:
            vals['cobr'] = cobr
        return vals

    @api.multi
    def _prepare_lote(self, lote, nfe_values):
        return {
            'idLote': lote,
            'indSinc': 0,
            'estado': self.company_id.partner_id.state_id.ibge_code,
            'ambiente': 1 if self.ambiente == 'producao' else 2,
            'NFes': [{
                'infNFe': nfe_values
            }],
            'modelo': self.model,
        }

    def _find_attachment_ids_email(self):
        atts = super(InvoiceEletronic, self)._find_attachment_ids_email()
        if self.model not in ('55'):
            return atts

        attachment_obj = self.env['ir.attachment']
        nfe_xml = base64.decodestring(self.nfe_processada)
        logo = base64.decodestring(self.invoice_id.company_id.logo)

        tmpLogo = io.BytesIO()
        tmpLogo.write(logo)
        tmpLogo.seek(0)

        xml_element = etree.fromstring(nfe_xml)
        oDanfe = danfe(list_xml=[xml_element], logo=tmpLogo)

        tmpDanfe = io.BytesIO()
        oDanfe.writeto_pdf(tmpDanfe)

        if danfe:
            danfe_id = attachment_obj.create(
                dict(
                    name="Danfe-%08d.pdf" % self.numero,
                    datas_fname="Danfe-%08d.pdf" % self.numero,
                    datas=base64.b64encode(tmpDanfe.getvalue()),
                    mimetype='application/pdf',
                    res_model='account.invoice',
                    res_id=self.invoice_id.id,
                ))
            atts.append(danfe_id.id)
        if nfe_xml:
            xml_id = attachment_obj.create(
                dict(
                    name=self.nfe_processada_name,
                    datas_fname=self.nfe_processada_name,
                    datas=base64.encodestring(nfe_xml),
                    mimetype='application/xml',
                    res_model='account.invoice',
                    res_id=self.invoice_id.id,
                ))
            atts.append(xml_id.id)
        return atts

    @api.multi
    def action_post_validate(self):
        super(InvoiceEletronic, self).action_post_validate()
        if self.model not in ('55', '65'):
            return
        chave_dict = {
            'cnpj': re.sub('[^0-9]', '', self.company_id.cnpj_cpf),
            'estado': self.company_id.state_id.ibge_code,
            'emissao': self.data_emissao[2:4] + self.data_emissao[5:7],
            'modelo': self.model,
            'numero': self.numero,
            'serie': self.serie.code.zfill(3),
            'tipo': int(self.tipo_emissao),
            'codigo': "%08d" % self.numero_controle
        }
        self.chave_nfe = gerar_chave(ChaveNFe(**chave_dict))

        cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file
        cert_pfx = base64.decodestring(cert)

        certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password)

        nfe_values = self._prepare_eletronic_invoice_values()

        lote = self._prepare_lote(self.id, nfe_values)
        xml_enviar = xml_autorizar_nfe(certificado, **lote)
        # _logger.info("============================xml_enviar before===========================%s", xml_enviar)
        # xml_enviar1 = xml_enviar.split('<dest>')[0]
        # xml_enviar2 = xml_enviar.split('<dest>')[1]
        # if self.ind_dest == '3':
        #    xml_enviar = xml_enviar1+'<dest><idEstrangeiro>'+re.sub('[^0-9]', '', self.partner_id.cnpj_cpf or '')+'</idEstrangeiro>'+xml_enviar2 if self.partner_id.cnpj_cpf else xml_enviar1+'<dest><idEstrangeiro/>'+xml_enviar2
        #    _logger.info("========================xml_enviar after===========================%s", xml_enviar)

        mensagens_erro = valida_nfe(xml_enviar)
        _logger.info(
            "========================mensagens_erro===========================%s",
            mensagens_erro)
        if mensagens_erro:
            raise UserError(mensagens_erro)

        self.xml_to_send = base64.encodestring(xml_enviar.encode('utf-8'))
        self.xml_to_send_name = 'nfse-enviar-%s.xml' % self.numero
        _logger.info(
            "========================self.xml_to_send_name===========================%s",
            self.xml_to_send_name)

    @api.multi
    def action_send_eletronic_invoice(self):
        super(InvoiceEletronic, self).action_send_eletronic_invoice()

        if self.model not in ('55', '65') or self.state in ('done', 'denied',
                                                            'cancel'):
            return

        self.state = 'error'
        self.data_emissao = datetime.now()

        cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file
        cert_pfx = base64.decodestring(cert)

        certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password)

        xml_to_send = base64.decodestring(self.xml_to_send).decode(
            'utf-8', 'ignore')
        import string
        _logger.info("----------before-------------%s", xml_to_send)
        xml_to_send = ''.join(x for x in xml_to_send if x in string.printable)
        xml_to_send = re.sub('\s+(?=<)', '', xml_to_send)
        xml_to_send = re.sub('\n', '', xml_to_send)
        _logger.info("----------xml_to_send-------------%s", xml_to_send)

        resposta_recibo = None
        resposta = autorizar_nfe(
            certificado,
            xml=xml_to_send,
            estado=self.company_id.state_id.ibge_code,
            ambiente=1 if self.ambiente == 'producao' else 2,
            modelo=self.model)
        retorno = resposta['object'].getchildren()[0]
        if retorno.cStat == 103:
            obj = {
                'estado': self.company_id.partner_id.state_id.ibge_code,
                'ambiente': 1 if self.ambiente == 'producao' else 2,
                'obj': {
                    'ambiente': 1 if self.ambiente == 'producao' else 2,
                    'numero_recibo': retorno.infRec.nRec
                },
                'modelo': self.model,
            }
            self.recibo_nfe = obj['obj']['numero_recibo']
            import time
            while True:
                time.sleep(2)
                resposta_recibo = retorno_autorizar_nfe(certificado, **obj)
                retorno = resposta_recibo['object'].getchildren()[0]
                if retorno.cStat != 105:
                    break

        if retorno.cStat != 104:
            self.codigo_retorno = retorno.cStat
            self.mensagem_retorno = retorno.xMotivo
        else:
            self.codigo_retorno = retorno.protNFe.infProt.cStat
            self.mensagem_retorno = retorno.protNFe.infProt.xMotivo
            if self.codigo_retorno == '100':
                self.write({
                    'state': 'done',
                    'protocolo_nfe': retorno.protNFe.infProt.nProt,
                    'data_autorizacao': retorno.protNFe.infProt.dhRecbto
                })
            # Duplicidade de NF-e significa que a nota já está emitida
            # TODO Buscar o protocolo de autorização, por hora só finalizar
            if self.codigo_retorno == '204':
                self.write({
                    'state': 'done',
                    'codigo_retorno': '100',
                    'mensagem_retorno': 'Autorizado o uso da NF-e'
                })

            # Denegada e nota já está denegada
            if self.codigo_retorno in ('302', '205'):
                self.write({'state': 'denied'})

        self.env['invoice.eletronic.event'].create({
            'code':
            self.codigo_retorno,
            'name':
            self.mensagem_retorno,
            'invoice_eletronic_id':
            self.id,
        })
        self._create_attachment('nfe-envio', self, resposta['sent_xml'])
        self._create_attachment('nfe-ret', self, resposta['received_xml'])
        recibo_xml = resposta['received_xml']
        if resposta_recibo:
            self._create_attachment('rec', self, resposta_recibo['sent_xml'])
            self._create_attachment('rec-ret', self,
                                    resposta_recibo['received_xml'])
            recibo_xml = resposta_recibo['received_xml']

        if self.codigo_retorno == '100':
            nfe_proc = gerar_nfeproc(resposta['sent_xml'], recibo_xml)
            self.nfe_processada = base64.encodestring(nfe_proc)
            self.nfe_processada_name = "NFe%08d.xml" % self.numero

    @api.multi
    def generate_nfe_proc(self):
        if self.state in ['cancel', 'done', 'denied']:
            recibo = self.env['ir.attachment'].search(
                [('res_model', '=', 'invoice.eletronic'),
                 ('res_id', '=', self.id), ('datas_fname', 'like', 'rec-ret')],
                limit=1)
            if not recibo:
                recibo = self.env['ir.attachment'].search(
                    [('res_model', '=', 'invoice.eletronic'),
                     ('res_id', '=', self.id),
                     ('datas_fname', 'like', 'nfe-ret')],
                    limit=1)
            nfe_envio = self.env['ir.attachment'].search(
                [('res_model', '=', 'invoice.eletronic'),
                 ('res_id', '=', self.id),
                 ('datas_fname', 'like', 'nfe-envio')],
                limit=1)
            if nfe_envio.datas and recibo.datas:
                nfe_proc = gerar_nfeproc(
                    base64.decodestring(nfe_envio.datas).decode('utf-8'),
                    base64.decodestring(recibo.datas).decode('utf-8'),
                )
                self.nfe_processada = base64.encodestring(nfe_proc)
                self.nfe_processada_name = "NFe%08d.xml" % self.numero
        else:
            raise UserError('A NFe não está validada')

    @api.multi
    def action_cancel_document(self, context=None, justificativa=None):
        if self.model not in ('55', '65'):
            return super(
                InvoiceEletronic,
                self).action_cancel_document(justificativa=justificativa)

        if not justificativa:
            return {
                'name': 'Cancelamento NFe',
                'type': 'ir.actions.act_window',
                'res_model': 'wizard.cancel.nfe',
                'view_type': 'form',
                'view_mode': 'form',
                'target': 'new',
                'context': {
                    'default_edoc_id': self.id
                }
            }

        cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file
        cert_pfx = base64.decodestring(cert)
        certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password)

        id_canc = "ID110111%s%02d" % (self.chave_nfe, self.sequencial_evento)

        tz = pytz.timezone(self.env.user.partner_id.tz) or pytz.utc
        dt_evento = datetime.utcnow()
        dt_evento = pytz.utc.localize(dt_evento).astimezone(tz)

        cancelamento = {
            'idLote':
            self.id,
            'estado':
            self.company_id.state_id.ibge_code,
            'ambiente':
            2 if self.ambiente == 'homologacao' else 1,
            'eventos': [{
                'Id':
                id_canc,
                'cOrgao':
                self.company_id.state_id.ibge_code,
                'tpAmb':
                2 if self.ambiente == 'homologacao' else 1,
                'CNPJ':
                re.sub('[^0-9]', '', self.company_id.cnpj_cpf),
                'chNFe':
                self.chave_nfe,
                'dhEvento':
                dt_evento.strftime('%Y-%m-%dT%H:%M:%S-03:00'),
                'nSeqEvento':
                self.sequencial_evento,
                'nProt':
                self.protocolo_nfe,
                'xJust':
                justificativa,
                'tpEvento':
                '110111',
                'descEvento':
                'Cancelamento',
            }],
            'modelo':
            self.model,
        }

        resp = recepcao_evento_cancelamento(certificado, **cancelamento)
        resposta = resp['object'].getchildren()[0]
        if resposta.cStat == 128 and \
                resposta.retEvento.infEvento.cStat in (135, 136, 155):
            self.state = 'cancel'
            self.codigo_retorno = resposta.retEvento.infEvento.cStat
            self.mensagem_retorno = resposta.retEvento.infEvento.xMotivo
            self.sequencial_evento += 1
        else:
            code, motive = None, None
            if resposta.cStat == 128:
                code = resposta.retEvento.infEvento.cStat
                motive = resposta.retEvento.infEvento.xMotivo
            else:
                code = resposta.cStat
                motive = resposta.xMotivo
            if code == 573:  # Duplicidade, já cancelado
                return self.action_get_status()

            return self._create_response_cancel(code, motive, resp,
                                                justificativa)

        self.env['invoice.eletronic.event'].create({
            'code':
            self.codigo_retorno,
            'name':
            self.mensagem_retorno,
            'invoice_eletronic_id':
            self.id,
        })
        self._create_attachment('canc', self, resp['sent_xml'])
        self._create_attachment('canc-ret', self, resp['received_xml'])
        nfe_processada = base64.decodestring(self.nfe_processada)

        nfe_proc_cancel = gerar_nfeproc_cancel(nfe_processada,
                                               resp['received_xml'].encode())
        if nfe_proc_cancel:
            self.nfe_processada = base64.encodestring(nfe_proc_cancel)

    def action_get_status(self):
        cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file
        cert_pfx = base64.decodestring(cert)
        certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password)
        consulta = {
            'estado': self.company_id.state_id.ibge_code,
            'ambiente': 2 if self.ambiente == 'homologacao' else 1,
            'modelo': self.model,
            'obj': {
                'chave_nfe': self.chave_nfe,
                'ambiente': 2 if self.ambiente == 'homologacao' else 1,
            }
        }
        resp = consultar_protocolo_nfe(certificado, **consulta)
        retorno_consulta = resp['object'].getchildren()[0]
        if retorno_consulta.cStat == 101:
            self.state = 'cancel'
            self.codigo_retorno = retorno_consulta.cStat
            self.mensagem_retorno = retorno_consulta.xMotivo
            resp['received_xml'] = etree.tostring(retorno_consulta,
                                                  encoding=str)

            self.env['invoice.eletronic.event'].create({
                'code':
                self.codigo_retorno,
                'name':
                self.mensagem_retorno,
                'invoice_eletronic_id':
                self.id,
            })
            self._create_attachment('canc', self, resp['sent_xml'])
            self._create_attachment('canc-ret', self, resp['received_xml'])
            nfe_processada = base64.decodestring(self.nfe_processada)

            nfe_proc_cancel = gerar_nfeproc_cancel(
                nfe_processada, resp['received_xml'].encode())
            if nfe_proc_cancel:
                self.nfe_processada = base64.encodestring(nfe_proc_cancel)
        else:
            message = "%s - %s" % (retorno_consulta.cStat,
                                   retorno_consulta.xMotivo)
            raise UserError(message)

    def _create_response_cancel(self, code, motive, response, justificativa):
        message = "%s - %s" % (code, motive)
        wiz = self.env['wizard.cancel.nfe'].create({
            'edoc_id':
            self.id,
            'justificativa':
            justificativa,
            'state':
            'error',
            'message':
            message,
            'sent_xml':
            base64.b64encode(response['sent_xml'].encode('utf-8')),
            'sent_xml_name':
            'cancelamento-envio.xml',
            'received_xml':
            base64.b64encode(response['received_xml'].encode('utf-8')),
            'received_xml_name':
            'cancelamento-retorno.xml',
        })
        return {
            'name': 'Cancelamento NFe',
            'type': 'ir.actions.act_window',
            'res_model': 'wizard.cancel.nfe',
            'res_id': wiz.id,
            'view_type': 'form',
            'view_mode': 'form',
            'target': 'new',
        }
Exemple #29
0
class PaymentLinkWizard(models.TransientModel):
    _name = "payment.link.wizard"
    _description = "Generate Payment Link"

    @api.model
    def default_get(self, fields):
        res = super(PaymentLinkWizard, self).default_get(fields)
        res_id = self._context.get('active_id')
        res_model = self._context.get('active_model')
        res.update({'res_id': res_id, 'res_model': res_model})
        amount_field = 'amount_residual' if res_model == 'account.move' else 'amount_total'
        if res_id and res_model == 'account.move':
            record = self.env[res_model].browse(res_id)
            res.update({
                'description': record.payment_reference,
                'amount': record[amount_field],
                'currency_id': record.currency_id.id,
                'partner_id': record.partner_id.id,
                'amount_max': record[amount_field],
            })
        return res

    res_model = fields.Char('Related Document Model', required=True)
    res_id = fields.Integer('Related Document ID', required=True)
    amount = fields.Monetary(currency_field='currency_id', required=True)
    amount_max = fields.Monetary(currency_field='currency_id')
    currency_id = fields.Many2one('res.currency')
    partner_id = fields.Many2one('res.partner')
    partner_email = fields.Char(related='partner_id.email')
    link = fields.Char(string='Payment Link', compute='_compute_values')
    description = fields.Char('Payment Ref')
    access_token = fields.Char(compute='_compute_values')
    company_id = fields.Many2one('res.company', compute='_compute_company')
    available_acquirer_ids = fields.Many2many(
        comodel_name='payment.acquirer',
        string="Payment Acquirers Available",
        compute='_compute_available_acquirer_ids',
        compute_sudo=True,
    )
    acquirer_id = fields.Many2one(
        comodel_name='payment.acquirer',
        string="Force Payment Acquirer",
        domain="[('id', 'in', available_acquirer_ids)]",
        help=
        "Force the customer to pay via the specified payment acquirer. Leave empty to allow the customer to choose among all acquirers."
    )
    has_multiple_acquirers = fields.Boolean(
        string="Has Multiple Acquirers",
        compute='_compute_has_multiple_acquirers',
    )

    @api.onchange('amount', 'description')
    def _onchange_amount(self):
        if float_compare(self.amount_max,
                         self.amount,
                         precision_rounding=self.currency_id.rounding
                         or 0.01) == -1:
            raise ValidationError(
                _("Please set an amount smaller than %s.") % (self.amount_max))
        if self.amount <= 0:
            raise ValidationError(
                _("The value of the payment amount must be positive."))

    @api.depends('amount', 'description', 'partner_id', 'currency_id',
                 'acquirer_id')
    def _compute_values(self):
        for payment_link in self:
            payment_link.access_token = payment_utils.generate_access_token(
                payment_link.partner_id.id, payment_link.amount,
                payment_link.currency_id.id)
        # must be called after token generation, obvsly - the link needs an up-to-date token
        self._generate_link()

    @api.depends('res_model', 'res_id')
    def _compute_company(self):
        for link in self:
            record = self.env[link.res_model].browse(link.res_id)
            link.company_id = record.company_id if 'company_id' in record else False

    @api.depends('company_id', 'partner_id', 'currency_id')
    def _compute_available_acquirer_ids(self):
        for link in self:
            link.available_acquirer_ids = link.env[
                'payment.acquirer']._get_compatible_acquirers(
                    company_id=link.company_id.id,
                    partner_id=link.partner_id.id,
                    currency_id=link.currency_id.id)

    @api.depends('available_acquirer_ids')
    def _compute_has_multiple_acquirers(self):
        for link in self:
            link.has_multiple_acquirers = len(link.available_acquirer_ids) > 1

    def _generate_link(self):
        for payment_link in self:
            related_document = self.env[payment_link.res_model].browse(
                payment_link.res_id)
            base_url = related_document.get_base_url(
            )  # Don't generate links for the wrong website
            payment_link.link = f'{base_url}/payment/pay' \
                   f'?reference={urls.url_quote(payment_link.description)}' \
                   f'&amount={payment_link.amount}' \
                   f'&currency_id={payment_link.currency_id.id}' \
                   f'&partner_id={payment_link.partner_id.id}' \
                   f'&company_id={payment_link.company_id.id}' \
                   f'{"&acquirer_id=" + str(payment_link.acquirer_id.id) if payment_link.acquirer_id else "" }' \
                   f'&access_token={payment_link.access_token}'
Exemple #30
0
class school_student(models.Model):
    _name = 'school.student'
    _description = 'school_student.school_student'
    _order = "school_id"

    name = fields.Char(default="Sunny Leaone")
    school_id = fields.Many2one("school.profile", string="School Name")
    hobby_list = fields.Many2many(
        "hobby",
        "school_hobby_rel",
        "student_id",
        "hobby_id",
        string="Hobby List",
    )
    is_virtual_school = fields.Boolean(related="school_id.is_virtual_class",
                                       string="Is Virtual Class",
                                       store=True)
    school_address = fields.Text(related="school_id.address",
                                 string="Address",
                                 help="This is school address.")
    currency_id = fields.Many2one("res.currency", string="Currency")
    student_fees = fields.Monetary(string="Student Fees", index=True)
    total_fees = fields.Float(string="Total Fees", default=200)
    ref_id = fields.Reference(selection=[('school.profile', 'School'),
                                         ('account.move', 'Invoice')],
                              string="Reference Field",
                              default="school.profile,1")
    active = fields.Boolean(string="Active", default=True)
    bdate = fields.Date(string="Date Of Birth")
    student_age = fields.Char(string="Total Age",
                              compute="_get_age_from_student")

    def wiz_open(self):

        return self.env['ir.actions.act_window']._for_xml_id(
            "school_student.student_fees_update_action")

        # return {'type': 'ir.actions.act_window',
        #         'res_model': 'student.feees.update.wizard',
        #         'view_mode': 'form',
        #         'target': 'new'}

    def custom_button_method(self):

        # self.env.cr.execute("insert into school_student(name, active) values('from button click', True)")
        # self.env.cr.commit()

        # self._cr.execute("insert into school_student(name, active) values('from button click', True)")
        # self._cr.commit()

        # print("Envi...... ",self.env)
        # print("user id...... ",self.env.uid)
        # print("current user...... ",self.env.user)
        # print("Super user?...... ",self.env.su)
        # print("Company...... ",self.env.company)
        # print("Compaies...... ",self.env.companies)
        # print("Lang...... ",self.env.lang)
        # print("Cr...... ",self.env.cr)
        # print("Hello this is custom_button_method called by you....", self)

        # with_env
        # with_context
        # with_user
        # with_company
        # sudo

        # self.env['student.test'].sudo().create({'name':'Student Test Demo.....'})

        # new_cr = registry(self.env.cr.dbname).cursor()
        # partner_id = self.env['res.partner'].with_env(self.env(cr=new_cr)).create({"name":" New Env CR Partner."})
        # partner_id.env.cr.commit()

        # self.custom_new_method(random.randint(1,1000))
        # self.custom_method()

        cli_commands = tl.config.options
        print(cli_commands)
        print(cli_commands.get("db_name"))
        print(cli_commands.get("db_user"))
        print(cli_commands.get("db_password"))
        print(cli_commands.get("addons_path"))
        print(cli_commands.get("dbfilter"))
        print(cli_commands.get("weblearns"))
        print(cli_commands.get("weblearns_author"))
        if tl.config.options.get("weblearns") == "'Tutorials'":
            tl.config.options['weblearns'] = "Odoo Tutorial"
        # print(cli_commands.get("weblearns"))
        # print(cli_commands.get("weblearns_author"))
        print(tl.config.options['weblearns'])

    def custom_new_method(self, total_fees):
        self.total_fees = total_fees

    def custom_method(self):
        try:
            self.ensure_one()
            print(self.name)
            print(self.bdate)
            print(self.school_id.name)
        except ValueError:
            pass

    @api.model
    def fields_view_get(self,
                        view_id=None,
                        view_type='form',
                        toolbar=False,
                        submenu=False):

        res = super(school_student, self).fields_view_get(view_id=view_id,
                                                          view_type=view_type,
                                                          toolbar=toolbar,
                                                          submenu=submenu)

        if view_type == "form":
            doc = etree.XML(res['arch'])
            name_field = doc.xpath("//field[@name='name']")
            if name_field:
                # Added one label in form view.
                name_field[0].addnext(
                    etree.Element(
                        'label', {
                            'string':
                            'Hello this is custom label from fields_view_get method'
                        }))

            #override attribute
            address_field = doc.xpath("//field[@name='school_address']")
            if address_field:
                address_field[0].set("string", "Hello This is School Address.")
                address_field[0].set("nolabel", "0")

            res['arch'] = etree.tostring(doc, encoding='unicode')

        if view_type == 'tree':
            doc = etree.XML(res['arch'])
            school_field = doc.xpath("//field[@name='school_id']")
            if school_field:
                # Added one field in tree view.
                school_field[0].addnext(
                    etree.Element('field', {
                        'string': 'Total Fees',
                        'name': 'total_fees'
                    }))
            res['arch'] = etree.tostring(doc, encoding='unicode')
        return res

    @api.model
    def default_get(self, field_list=[]):
        print("field_list ", field_list)
        rtn = super(school_student, self).default_get(field_list)
        print("Befor Edit ", rtn)
        rtn['student_fees'] = 4000
        print("return statement ", rtn)
        return rtn

    @api.depends("bdate")
    def _get_age_from_student(self):
        """Age Calculation"""
        today_date = datetime.date.today()
        for stud in self:
            if stud.bdate:
                """
                Get only year.
                """
                # bdate = fields.Datetime.to_datetime(stud.bdate).date()
                # total_age = str(int((today_date - bdate).days / 365))
                # stud.student_age = total_age
                """
                Origin of below source code
                https://gist.github.com/shahri23/1804a3acb7ffb58a1ec8f1eda304af1a
                """
                currentDate = datetime.date.today()

                deadlineDate = fields.Datetime.to_datetime(stud.bdate).date()
                print(deadlineDate)
                daysLeft = currentDate - deadlineDate
                print(daysLeft)

                years = ((daysLeft.total_seconds()) / (365.242 * 24 * 3600))
                yearsInt = int(years)

                months = (years - yearsInt) * 12
                monthsInt = int(months)

                days = (months - monthsInt) * (365.242 / 12)
                daysInt = int(days)

                hours = (days - daysInt) * 24
                hoursInt = int(hours)

                minutes = (hours - hoursInt) * 60
                minutesInt = int(minutes)

                seconds = (minutes - minutesInt) * 60
                secondsInt = int(seconds)

                stud.student_age = 'You are {0:d} years, {1:d}  months, {2:d}  days, {3:d}  hours, {4:d} \
                 minutes, {5:d} seconds old.'.format(yearsInt, monthsInt,
                                                     daysInt, hoursInt,
                                                     minutesInt, secondsInt)
            else:
                stud.student_age = "Not Providated...."