Files
Alexis de Lattre bdf4a527de [MIG] account_usability_akretion to v18
Move code from account_usability_akretion to base_usability_akretion
2024-12-31 01:02:50 +01:00

194 lines
8.1 KiB
Python

# Copyright 2015-2024 Akretion France (https://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import timedelta
import logging
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.tools import float_is_zero
from odoo.tools.misc import format_date
_logger = logging.getLogger(__name__)
class AccountMove(models.Model):
_inherit = 'account.move'
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)")
# The native "blocked" field (bool) on account.move.line has been removed in v18
# 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
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
# <tree editable="bottom" default_order="sequence, date desc, move_name desc, id"
# it's not the same as the _order in the class AccountMoveLine
lines = self.env['account.move.line'].search([('display_type', 'in', ('product', 'line_section', 'line_note')), ('move_id', '=', self.id)], order="sequence, date desc, move_name desc, id")
for line in lines:
if line.display_type == 'line_section':
# insert line
if has_sections:
res.append({'subtotal': subtotal})
subtotal = 0.0 # reset counter
has_sections = True
else:
if line.display_type == 'product':
subtotal += line.price_subtotal * sign
res.append({'line': line})
if has_sections: # insert last subtotal line
res.append({'subtotal': subtotal})
# res:
# [
# {'line': account_invoice_line(1) with display_type=='line_section'},
# {'line': account_invoice_line(2) with display_type=='product'},
# {'line': account_invoice_line(3) with display_type=='product'},
# {'line': account_invoice_line(4) with display_type=='line_note'},
# {'subtotal': 8932.23},
# ]
return res
def _compute_sales_dates(self):
""" French law requires to set sale order dates into invoice
returned string: "sale1 (date1), sale2 (date2) ..."
"""
for move in self:
sales = move.invoice_line_ids.mapped(
'sale_line_ids').mapped('order_id')
dates = ["%s (%s)" % (
x.name, format_date(move.env, x.date_order))
for x in sales]
move.sale_dates = ", ".join(dates)
# allow to manually create moves not only in general journals,
# but also in cash journal and check journals (= bank journals not linked to a bank account)
@api.depends('company_id', 'invoice_filter_type_domain')
def _compute_suitable_journal_ids(self):
for move in self:
if move.invoice_filter_type_domain:
super(AccountMove, move)._compute_suitable_journal_ids()
else:
company_id = move.company_id.id or self.env.company.id
domain = expression.AND([
[('company_id', '=', company_id)],
expression.OR([
[('type', 'in', ('general', 'cash'))],
[('type', '=', 'bank'), ('bank_account_id', '=', False)]
])
])
move.suitable_journal_ids = self.env['account.journal'].search(domain)
# There is no more attachment by default on invoice reports...
# TODO check what's the editor strategy on this
# def button_draft(self):
# Delete attached pdf invoice
# for move in self.filtered(lambda x: x.move_type in ('out_invoice', 'out_refund')):
# for report_xmlid in ('account.account_invoices', 'account.account_invoices_without_payment'):
# report = self.env.ref(report_xmlid)
# attach = report.retrieve_attachment(move)
# if attach:
# attach.unlink()
# super().button_draft()
def _get_accounting_date(self, invoice_date, has_tax, lock_dates=None):
# On vendor bills/refunds, we want date = invoice_date unless
# we have a company tax_lock_date and the invoice has taxes
# and invoice_date <= tax_lock_date
date = super()._get_accounting_date(invoice_date, has_tax, lock_dates=lock_dates)
lock_dates = lock_dates or self._get_violated_lock_dates(invoice_date, has_tax)
if self.is_purchase_document(include_receipts=True) and invoice_date:
if lock_dates:
date = max([entry[0] for entry in lock_dates]) + timedelta(1)
else:
date = invoice_date
return date