1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/account_3way_match/models/account_invoice.py
2024-12-10 09:04:09 +07:00

177 lines
9.2 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.tools.float_utils import float_compare
from odoo.tools.sql import column_exists, create_column
# Available values for the release_to_pay field.
_release_to_pay_status_list = [('yes', 'Yes'), ('no', 'No'), ('exception', 'Exception')]
class AccountMove(models.Model):
_inherit = 'account.move'
def _auto_init(self):
if not column_exists(self.env.cr, "account_move", "release_to_pay"):
# Create column manually to set default value to 'exception' on postgres level.
# This way we avoid heavy computation on module installation.
self.env.cr.execute("ALTER TABLE account_move ADD COLUMN release_to_pay VARCHAR DEFAULT 'exception'")
return super()._auto_init()
release_to_pay = fields.Selection(
_release_to_pay_status_list,
compute='_compute_release_to_pay',
copy=False,
store=True,
help="This field can take the following values :\n"
" * Yes: you should pay the bill, you have received the products\n"
" * No, you should not pay the bill, you have not received the products\n"
" * Exception, there is a difference between received and billed quantities\n"
"This status is defined automatically, but you can force it by ticking the 'Force Status' checkbox.")
release_to_pay_manual = fields.Selection(
_release_to_pay_status_list,
string='Should Be Paid',
compute='_compute_release_to_pay_manual', store='True', readonly=False,
help=" * Yes: you should pay the bill, you have received the products\n"
" * No, you should not pay the bill, you have not received the products\n"
" * Exception, there is a difference between received and billed quantities\n"
"This status is defined automatically, but you can force it by ticking the 'Force Status' checkbox.")
force_release_to_pay = fields.Boolean(
string="Force Status",
help="Indicates whether the 'Should Be Paid' status is defined automatically or manually.")
@api.depends('invoice_line_ids.can_be_paid', 'force_release_to_pay', 'payment_state')
def _compute_release_to_pay(self):
records = self
if self.env.context.get('module') == 'account_3way_match':
# on module installation we set 'no' for all paid bills and other non relevant records at once
records = records.filtered(lambda r: r.payment_state != 'paid' and r.move_type in ('in_invoice', 'in_refund'))
(self - records).release_to_pay = 'no'
for invoice in records:
if invoice.payment_state == 'paid' or not invoice.is_invoice(include_receipts=True):
# no need to pay, if it's already paid
invoice.release_to_pay = 'no'
elif invoice.force_release_to_pay:
#we must use the manual value contained in release_to_pay_manual
invoice.release_to_pay = invoice.release_to_pay_manual
else:
#otherwise we must compute the field
result = None
for invoice_line in invoice.invoice_line_ids.filtered(lambda l: l.display_type not in ('line_section', 'line_note')):
line_status = invoice_line.can_be_paid
if line_status == 'exception':
#If one line is in exception, the entire bill is
result = 'exception'
break
elif not result:
result = line_status
elif line_status != result:
result = 'exception'
break
#The last two elif conditions model the fact that a
#bill will be in exception if its lines have different status.
#Otherwise, its status will be the one all its lines share.
#'result' can be None if the bill was entirely empty.
invoice.release_to_pay = result or 'no'
@api.depends('release_to_pay', 'force_release_to_pay')
def _compute_release_to_pay_manual(self):
for invoice in self:
if not (invoice.payment_state == 'paid' or not invoice.is_invoice(include_receipts=True) or invoice.force_release_to_pay):
invoice.release_to_pay_manual = invoice.release_to_pay
@api.onchange('release_to_pay_manual')
def _onchange_release_to_pay_manual(self):
if self.release_to_pay and self.release_to_pay_manual != self.release_to_pay:
self.force_release_to_pay = True
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
def _auto_init(self):
if not column_exists(self.env.cr, "account_move_line", "can_be_paid"):
# Create column manually to set default value to 'exception' on postgres level.
# This way we avoid heavy computation on module installation.
self.env.cr.execute("ALTER TABLE account_move_line ADD COLUMN can_be_paid VARCHAR DEFAULT 'exception'")
return super()._auto_init()
@api.depends('purchase_line_id.qty_received', 'purchase_line_id.qty_invoiced', 'purchase_line_id.product_qty', 'price_unit')
def _can_be_paid(self):
""" Computes the 'release to pay' status of an invoice line, depending on
the invoicing policy of the product linked to it, by calling the dedicated
subfunctions. This function also ensures the line is linked to a purchase
order (otherwise, can_be_paid will be set as 'exception'), and the price
between this order and the invoice did not change (otherwise, again,
the line is put in exception).
"""
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for invoice_line in self:
po_line = invoice_line.purchase_line_id
if po_line:
invoiced_qty = po_line.qty_invoiced
received_qty = po_line.qty_received
ordered_qty = po_line.product_qty
# A price difference between the original order and the invoice results in an exception
invoice_currency = invoice_line.currency_id
order_currency = po_line.currency_id
invoice_converted_price = invoice_currency._convert(
invoice_line.price_unit, order_currency, invoice_line.company_id, fields.Date.today())
if order_currency.compare_amounts(po_line.price_unit, invoice_converted_price) != 0:
invoice_line.can_be_paid = 'exception'
continue
if po_line.product_id.purchase_method == 'purchase': # 'on ordered quantities'
invoice_line._can_be_paid_ordered_qty(invoiced_qty, received_qty, ordered_qty, precision)
else: # 'on received quantities'
invoice_line._can_be_paid_received_qty(invoiced_qty, received_qty, ordered_qty, precision)
else: # Serves as default if the line is not linked to any Purchase.
invoice_line.can_be_paid = 'exception'
def _can_be_paid_ordered_qty(self, invoiced_qty, received_qty, ordered_qty, precision):
"""
Gives the release_to_pay status of an invoice line for 'on ordered
quantity' billing policy, if this line's invoice is related to a purchase order.
This function sets can_be_paid field to one of the following:
'yes': the content of the line has been ordered and can be invoiced
'no' : the content of the line hasn't been ordered at all, and cannot be invoiced
'exception' : only part of the invoice has been ordered
"""
if float_compare(invoiced_qty - self.quantity, ordered_qty, precision_digits=precision) >= 0:
self.can_be_paid = 'no'
elif float_compare(invoiced_qty, ordered_qty, precision_digits=precision) <= 0:
self.can_be_paid = 'yes'
else:
self.can_be_paid = 'exception'
def _can_be_paid_received_qty(self, invoiced_qty, received_qty, ordered_qty, precision):
"""
Gives the release_to_pay status of an invoice line for 'on received
quantity' billing policy, if this line's invoice is related to a purchase order.
This function sets can_be_paid field to one of the following:
'yes': the content of the line has been received and can be invoiced
'no' : the content of the line hasn't been received at all, and cannot be invoiced
'exception' : ordered and received quantities differ
"""
if float_compare(invoiced_qty, received_qty, precision_digits=precision) <= 0:
self.can_be_paid = 'yes'
elif received_qty == 0 and float_compare(invoiced_qty, ordered_qty, precision_digits=precision) <= 0: # "and" part to ensure a too high billed quantity results in an exception:
self.can_be_paid = 'no'
else:
self.can_be_paid = 'exception'
can_be_paid = fields.Selection(
_release_to_pay_status_list,
compute='_can_be_paid',
copy=False,
store=True,
string='Release to Pay')