OCA/l10n-italy

View on GitHub
l10n_it_fatturapa_in/models/account.py

Summary

Maintainability
F
3 days
Test Coverage
# -*- coding: utf-8 -*-

from openerp import fields, models, api, _
from openerp.exceptions import ValidationError, Warning as UserError
from openerp.tools import float_compare
import openerp.addons.decimal_precision as dp


class AccountInvoice(models.Model):
    _inherit = "account.invoice"

    fatturapa_attachment_in_id = fields.Many2one(
        'fatturapa.attachment.in', 'E-bill Import File',
        ondelete='restrict', copy=False)
    inconsistencies = fields.Text('Import Inconsistencies', copy=False)
    e_invoice_line_ids = fields.One2many(
        "einvoice.line", "invoice_id", string="Lines Detail",
        readonly=True, copy=False)

    e_invoice_amount_untaxed = fields.Float(
        string='E-Invoice Untaxed Amount', readonly=True)
    e_invoice_amount_tax = fields.Float(string='E-Invoice Tax Amount',
                                        readonly=True)
    e_invoice_amount_total = fields.Float(string='E-Invoice Total Amount',
                                          readonly=True)

    e_invoice_reference = fields.Char(
        string="E-invoice vendor reference",
        readonly=True)

    e_invoice_date_invoice = fields.Date(
        string="E-invoice date",
        readonly=True)

    e_invoice_validation_error = fields.Boolean(
        compute='_compute_e_invoice_validation_error')

    e_invoice_validation_message = fields.Text(
        compute='_compute_e_invoice_validation_error')

    e_invoice_force_validation = fields.Boolean(
        string='Force E-Invoice Validation')

    # 'tax_line.amount_rounding'
    # TODO field 'amount_rounding' non present in v8 account.invoice.tax
    @api.one
    @api.depends('invoice_line.price_subtotal', 'tax_line.amount',
                 'currency_id', 'company_id',
                 'date_invoice', 'type', 'efatt_rounding')
    def _compute_amount(self):
        super(AccountInvoice, self)._compute_amount()
        if self.efatt_rounding != 0:
            self.amount_total += self.efatt_rounding
            amount_total_company_signed = self.amount_total
            if self.currency_id and self.company_id and self.currency_id !=\
                    self.company_id.currency_id:
                currency_id = self.currency_id
                amount_total_company_signed = currency_id._convert(
                    self.amount_total, self.company_id.currency_id,
                    self.company_id, self.date_invoice or fields.Date.today())
            sign = self.type in ['in_refund', 'out_refund'] and -1 or 1
            self.amount_total_company_signed = amount_total_company_signed * sign
            self.amount_total_signed = self.amount_total * sign

    @api.multi
    def action_move_create(self):
        """Append global rounding move lines"""
        res = super(AccountInvoice, self).action_move_create()
        for invoice in self:
            if invoice.efatt_rounding != 0:
                if invoice.efatt_rounding > 0:
                    arrotondamenti_account_id = self.env.user.company_id.\
                        arrotondamenti_passivi_account_id
                    if not arrotondamenti_account_id:
                        raise UserError(_("Round down account is not set "
                                          "in Accounting Settings"))
                    name = _("Rounding down")
                else:
                    arrotondamenti_account_id = self.env.user.company_id.\
                        arrotondamenti_attivi_account_id
                    if not arrotondamenti_account_id:
                        raise UserError(_("Round up account is not set "
                                          "in Accounting Settings"))
                    name = _("Rounding up")
                line_model = self.env['account.move.line']
                line_vals = {
                    'type': 'global_rounding',
                    'name': name,
                    'price_unit': invoice.efatt_rounding,
                    'quantity': 1,
                    'price': invoice.efatt_rounding,
                    'account_id': arrotondamenti_account_id.id,
                    'invoice_id': invoice.id,
                    'move_id': invoice.move_id.id,
                }
                line_model.create(line_vals)
        return res

    @api.multi
    def invoice_validate(self):
        for invoice in self:
            if (invoice.e_invoice_validation_error and
                    not invoice.e_invoice_force_validation):
                raise ValidationError(
                    _("The invoice '%s' doesn't match the related e-invoice") %
                    invoice.display_name)
        return super(AccountInvoice, self).invoice_validate()

    def e_inv_check_amount_untaxed(self):
        error_message = ''
        if (self.e_invoice_amount_untaxed and
                float_compare(self.amount_untaxed,
                              # Using abs because odoo invoice total can't be negative,
                              # while XML total can.
                              # See process_negative_lines method
                              abs(self.e_invoice_amount_untaxed),
                              precision_rounding=self.currency_id
                              .rounding) != 0):
            error_message = (
                _("Untaxed amount ({bill_amount_untaxed}) "
                  "does not match with "
                  "e-bill untaxed amount ({e_bill_amount_untaxed})")
                .format(
                    bill_amount_untaxed=self.amount_untaxed or 0,
                    e_bill_amount_untaxed=self.e_invoice_amount_untaxed
                ))
        return error_message

    def e_inv_check_amount_tax(self):
        error_message = ''
        if (self.e_invoice_amount_tax and
                float_compare(self.amount_tax,
                              abs(self.e_invoice_amount_tax),
                              precision_rounding=self.currency_id
                              .rounding) != 0):
            error_message = (
                _("Taxed amount ({bill_amount_tax}) "
                  "does not match with "
                  "e-bill taxed amount ({e_bill_amount_tax})")
                .format(
                    bill_amount_tax=self.amount_tax or 0,
                    e_bill_amount_tax=self.e_invoice_amount_tax
                ))
        return error_message

    def e_inv_check_amount_total(self):
        error_message = ''
        if (self.e_invoice_amount_total and
                float_compare(self.amount_total,
                              abs(self.e_invoice_amount_total),
                              precision_rounding=self.currency_id
                              .rounding) != 0):
            error_message = (
                _("Total amount ({bill_amount_total}) "
                  "does not match with "
                  "e-bill total amount ({e_bill_amount_total})")
                .format(
                    bill_amount_total=self.amount_total or 0,
                    e_bill_amount_total=self.e_invoice_amount_total
                ))
        return error_message

    def e_inv_dati_ritenuta(self):
        error_message = ''
        # ftpa_withholding_type is set when DatiRitenuta is set,
        # withholding_tax is not set if no lines with Ritenuta = SI are found
        if self.ftpa_withholding_ids and not self.withholding_tax:
            error_message += (_(
                "E-bill contains DatiRitenuta but no lines subjected to Ritenuta was "
                "found. Please manually check Withholding tax Amount\n"
            ))
        if sum(self.ftpa_withholding_ids.mapped('amount'))\
                != self.withholding_tax_amount:
            error_message += (_(
                "E-bill contains ImportoRitenuta %s but created invoice has got"
                " %s\n" % (
                    sum(self.ftpa_withholding_ids.mapped('amount')),
                    self.withholding_tax_amount
                )
            ))
        return error_message

    @api.depends('type', 'state', 'fatturapa_attachment_in_id',
                 'amount_untaxed', 'amount_tax', 'amount_total',
                 'reference', 'date_invoice')
    def _compute_e_invoice_validation_error(self):
        bills_to_check = self.filtered(
            lambda inv:
                inv.type in ['in_invoice', 'in_refund'] and
                inv.state in ['draft', 'open', 'paid'] and
                inv.fatturapa_attachment_in_id)
        for bill in bills_to_check:
            error_messages = list()

            error_message = bill.e_inv_check_amount_untaxed()
            if error_message:
                error_messages.append(error_message)

            error_message = bill.e_inv_check_amount_tax()
            if error_message:
                error_messages.append(error_message)

            error_message = bill.e_inv_check_amount_total()
            if error_message:
                error_messages.append(error_message)

            error_message = bill.e_inv_dati_ritenuta()
            if error_message:
                error_messages.append(error_message)

            if (bill.e_invoice_reference and
                    bill.reference != bill.e_invoice_reference):
                error_messages.append(
                    _("Vendor reference ({bill_vendor_ref}) "
                      "does not match with "
                      "e-bill vendor reference ({e_bill_vendor_ref})")
                    .format(
                        bill_vendor_ref=bill.reference or "",
                        e_bill_vendor_ref=bill.e_invoice_reference
                    ))

            if (bill.e_invoice_date_invoice and
                    bill.e_invoice_date_invoice != bill.date_invoice):
                error_messages.append(
                    _("Invoice date ({bill_date_invoice}) "
                      "does not match with "
                      "e-bill invoice date ({e_bill_date_invoice})")
                    .format(
                        bill_date_invoice=bill.date_invoice or "",
                        e_bill_date_invoice=bill.e_invoice_date_invoice
                    ))

            if not error_messages:
                continue
            bill.e_invoice_validation_error = True
            bill.e_invoice_validation_message = \
                ",\n".join(error_messages) + "."

    @api.multi
    def name_get(self):
        result = super(AccountInvoice, self).name_get()
        res = []
        for tup in result:
            invoice = self.browse(tup[0])
            if invoice.type in ('in_invoice', 'in_refund'):
                name = "%s, %s" % (tup[1], invoice.partner_id.name)
                if invoice.origin:
                    name += ', %s' % invoice.origin
                res.append((invoice.id, name))
            else:
                res.append(tup)
        return res

    @api.multi
    def remove_attachment_link(self):
        self.ensure_one()
        self.fatturapa_attachment_in_id = False
        return {'type': 'ir.actions.client', 'tag': 'reload'}

    @api.model
    def compute_xml_amount_untaxed(self, FatturaBody):
        amount_untaxed = 0.0
        for Riepilogo in FatturaBody.DatiBeniServizi.DatiRiepilogo:
            amount_untaxed += float(Riepilogo.ImponibileImporto or 0.0)
        return amount_untaxed

    @api.model
    def compute_xml_amount_total(self, FatturaBody, amount_untaxed, amount_tax):
        rounding = float(
            FatturaBody.DatiGenerali.DatiGeneraliDocumento.Arrotondamento
            or 0.0)
        return amount_untaxed + amount_tax + rounding

    @api.model
    def compute_xml_amount_tax(self, DatiRiepilogo):
        amount_tax = 0.0
        for Riepilogo in DatiRiepilogo:
            amount_tax += float(Riepilogo.Imposta or 0.0)
        return amount_tax

    def set_einvoice_data(self, fattura):
        self.ensure_one()
        amount_untaxed = self.compute_xml_amount_untaxed(fattura)
        amount_tax = self.compute_xml_amount_tax(
            fattura.DatiBeniServizi.DatiRiepilogo)
        amount_total = self.compute_xml_amount_total(
            fattura, amount_untaxed, amount_tax)
        reference = fattura.DatiGenerali.DatiGeneraliDocumento.Numero
        date_invoice = fattura.DatiGenerali.DatiGeneraliDocumento.Data

        self.update({
            'e_invoice_amount_untaxed': amount_untaxed,
            'e_invoice_amount_tax': amount_tax,
            'e_invoice_amount_total': amount_total,
            'e_invoice_reference': reference,
            'e_invoice_date_invoice': date_invoice,
        })

    def process_negative_lines(self):
        self.ensure_one()
        for line in self.invoice_line:
            if line.price_unit >= 0:
                return
        # if every line is negative, change them all
        for line in self.invoice_line:
            line.price_unit = -line.price_unit
        self.button_reset_taxes()


class FatturapaArticleCode(models.Model):
    # _position = ['2.2.1.3']
    _name = "fatturapa.article.code"
    _description = 'E-bill Article Code'

    name = fields.Char('Code Type')
    code_val = fields.Char('Code Value')
    e_invoice_line_id = fields.Many2one(
        'einvoice.line', 'Related E-bill Line', readonly=True
    )


class AccountInvoiceLine(models.Model):
    # _position = [
    #     '2.2.1.3', '2.2.1.6', '2.2.1.7',
    #     '2.2.1.8', '2.1.1.10'
    # ]
    _inherit = "account.invoice.line"

    fatturapa_attachment_in_id = fields.Many2one(
        'fatturapa.attachment.in', 'E-bill Import File',
        readonly=True, related='invoice_id.fatturapa_attachment_in_id')


class DiscountRisePrice(models.Model):
    _inherit = "discount.rise.price"
    e_invoice_line_id = fields.Many2one(
        'einvoice.line', 'Related E-bill Line', readonly=True
    )


class EInvoiceLine(models.Model):
    _name = 'einvoice.line'
    _description = 'E-invoice line'
    invoice_id = fields.Many2one(
        "account.invoice", "Bill", readonly=True, ondelete='cascade')
    line_number = fields.Integer('Line Number', readonly=True)
    service_type = fields.Char('Sale Provision Type', readonly=True)
    cod_article_ids = fields.One2many(
        'fatturapa.article.code', 'e_invoice_line_id',
        'Articles Code', readonly=True
    )
    name = fields.Char("Description", readonly=True)
    qty = fields.Float(
        "Quantity", readonly=True,
        digits=dp.get_precision('Product Unit of Measure')
    )
    uom = fields.Char("Unit of measure", readonly=True)
    period_start_date = fields.Date("Period Start Date", readonly=True)
    period_end_date = fields.Date("Period End Date", readonly=True)
    unit_price = fields.Float(
        "Unit Price", readonly=True,
        digits=dp.get_precision('Product Price')
    )
    discount_rise_price_ids = fields.One2many(
        'discount.rise.price', 'e_invoice_line_id',
        'Discount and Supplement Details', readonly=True
    )
    total_price = fields.Float("Total Price", readonly=True)
    tax_amount = fields.Float("VAT Rate", readonly=True)
    wt_amount = fields.Char("Tax Withholding", readonly=True)
    tax_kind = fields.Char("Nature", readonly=True)
    admin_ref = fields.Char("Administration Reference", readonly=True)
    other_data_ids = fields.One2many(
        "einvoice.line.other.data", "e_invoice_line_id",
        string="Other Administrative Data", readonly=True)


class EInvoiceLineOtherData(models.Model):
    _name = 'einvoice.line.other.data'
    _description = 'E-invoice line other data'

    e_invoice_line_id = fields.Many2one(
        'einvoice.line', 'Related E-bill line', readonly=True
    )
    name = fields.Char("Data Type", readonly=True)
    text_ref = fields.Char("Text Reference", readonly=True)
    num_ref = fields.Float("Number Reference", readonly=True)
    date_ref = fields.Char("Date Reference", readonly=True)