# Copyright 2015-2022 Akretion (http://www.akretion.com) # @author Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import timedelta from collections import defaultdict import logging from odoo import api, fields, models, _ from odoo.exceptions import UserError from odoo.osv import expression from odoo.tools import float_is_zero from odoo.tools.misc import format_date from odoo.tools.safe_eval import safe_eval, time _logger = logging.getLogger(__name__) class AccountMove(models.Model): _inherit = 'account.move' # By default, we can still modify "ref" when account move is posted # which seems a bit lazy for me... ref = fields.Char(states={'posted': [('readonly', True)]}) invoice_date_due = fields.Date(tracking=True) invoice_payment_term_id = fields.Many2one(tracking=True) journal_id = fields.Many2one(tracking=True) fiscal_position_id = fields.Many2one(tracking=True) amount_total = fields.Monetary(tracking=True) # for invoice report has_discount = fields.Boolean(compute='_compute_has_discount') # has_attachment is useful for those who use attachment to archive # supplier invoices. It allows them to find supplier invoices # that don't have any attachment has_attachment = fields.Boolean( compute='_compute_has_attachment', search='_search_has_attachment') sale_dates = fields.Char( compute="_compute_sales_dates", help="This information appear on invoice qweb report " "(you may use it for your own report)") # There is a native "blocked" field (bool) on account.move.line # We want to have that field on invoices to improve usability # while keeping compatibility with the standard Odoo datamodel blocked = fields.Boolean( compute="_compute_blocked", inverse="_inverse_blocked", store=True, string="Dispute", tracking=True, ) # Field search_account_id is just for search view search_account_id = fields.Many2one(related='line_ids.account_id') @api.depends("line_ids", "line_ids.blocked") def _compute_blocked(self): for move in self: move.blocked = any( [ l.blocked for l in move.line_ids if l.account_id.account_type in ("liability_payable", "asset_receivable") ] ) def _inverse_blocked(self): for move in self: for line in move.line_ids.filtered( lambda l: l.account_id.account_type in ("liability_payable", "asset_receivable") ): line.blocked = move.blocked def _compute_has_discount(self): prec = self.env['decimal.precision'].precision_get('Discount') for inv in self: has_discount = False for line in inv.invoice_line_ids: if line.display_type == 'product' and not float_is_zero(line.discount, precision_digits=prec): has_discount = True break inv.has_discount = has_discount def _compute_has_attachment(self): iao = self.env['ir.attachment'] for move in self: if iao.search_count([ ('res_model', '=', 'account.move'), ('res_id', '=', move.id), ('type', '=', 'binary'), ('company_id', '=', move.company_id.id)]): move.has_attachment = True else: move.has_attachment = False def _search_has_attachment(self, operator, value): att_inv_ids = {} if operator == '=': search_res = self.env['ir.attachment'].search_read([ ('res_model', '=', 'account.move'), ('type', '=', 'binary'), ('res_id', '!=', False)], ['res_id']) for att in search_res: att_inv_ids[att['res_id']] = True res = [('id', value and 'in' or 'not in', list(att_inv_ids))] return res # when you have an invoice created from a lot of sale orders, the 'name' # field is very large, which makes the name_get() of that invoice very big # which screws-up the form view of that invoice because of the link at the # top of the screen # That's why we have to cut the name_get() when it's too long def name_get(self): old_res = super().name_get() res = [] for old_re in old_res: name = old_re[1] if name and len(name) > 100: # nice cut name = '%s ...' % ', '.join(name.split(', ')[:3]) # if not enough, hard cut if len(name) > 120: name = '%s ...' % old_re[1][:120] res.append((old_re[0], name)) return res def _reverse_moves(self, default_values_list=None, cancel=False): reverse_moves = super()._reverse_moves( default_values_list=default_values_list, cancel=cancel) # In the simple scenario 1 invoice -> 1 refund, we add a message in the chatter # of the invoice and in the chatter of the refund if len(self) == 1 and len(reverse_moves) == 1: self.message_post(body=_("A reverse journal entry %s has been generated.") % (reverse_moves.id, reverse_moves.display_name)) reverse_moves.message_post(body=_("This journal entry has been generated as the reverse of %s.") % (self.id, self.display_name)) return reverse_moves def delete_lines_qty_zero(self): lines = self.env['account.move.line'].search([ ('display_type', '=', 'product'), ('move_id', 'in', self.ids), ('quantity', '=', 0)]) lines.unlink() return True # for report def py3o_lines_layout(self): self.ensure_one() res = [] has_sections = False subtotal = 0.0 sign = self.move_type == 'out_refund' and -1 or 1 # Warning: the order of invoice line is forced in the view #