238 lines
9.3 KiB
Python
238 lines
9.3 KiB
Python
from odoo import models, fields, api
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools import float_compare
|
|
|
|
|
|
class PurchaseOrder(models.Model):
|
|
_inherit = 'purchase.order'
|
|
|
|
advance_payment_ids = fields.One2many(
|
|
'account.payment',
|
|
'purchase_order_id',
|
|
string='Advance Payments',
|
|
domain=[('state', '=', 'posted')]
|
|
)
|
|
|
|
advance_payment_total = fields.Monetary(
|
|
string='Advance Payment Total',
|
|
compute='_compute_advance_payment_total',
|
|
store=True
|
|
)
|
|
|
|
amount_residual = fields.Monetary(
|
|
string='Amount Residual',
|
|
compute='_compute_amount_residual',
|
|
store=True
|
|
)
|
|
|
|
deposit_product_id = fields.Many2one(
|
|
'product.product',
|
|
string='Deposit Product',
|
|
help='Product used for advance payment deposit'
|
|
)
|
|
|
|
@api.depends('advance_payment_ids', 'advance_payment_ids.state', 'advance_payment_ids.amount')
|
|
def _compute_advance_payment_total(self):
|
|
for order in self:
|
|
order.advance_payment_total = sum(
|
|
payment.amount for payment in order.advance_payment_ids.filtered(lambda p: p.state == 'posted')
|
|
)
|
|
|
|
@api.depends('amount_total', 'advance_payment_total')
|
|
def _compute_amount_residual(self):
|
|
for order in self:
|
|
order.amount_residual = order.amount_total - order.advance_payment_total
|
|
|
|
def action_view_advance_payments(self):
|
|
self.ensure_one()
|
|
action = self.env.ref('account.action_account_payments').sudo().read()[0]
|
|
action['domain'] = [('id', 'in', self.advance_payment_ids.ids)]
|
|
action['context'] = {
|
|
'default_purchase_order_id': self.id,
|
|
'default_partner_id': self.partner_id.id,
|
|
'default_payment_type': 'outbound',
|
|
'default_partner_type': 'supplier',
|
|
}
|
|
return action
|
|
|
|
def action_create_deposit_product(self):
|
|
"""Create a deposit product for this purchase order"""
|
|
self.ensure_one()
|
|
# Check if there's a default deposit product in settings
|
|
default_deposit_product = self.env['ir.config_parameter'].sudo().get_param(
|
|
'purchase_advance_payment.deposit_product_id')
|
|
if default_deposit_product:
|
|
self.deposit_product_id = int(default_deposit_product)
|
|
return self.deposit_product_id
|
|
|
|
# If no default product, create one
|
|
if not self.deposit_product_id:
|
|
product_vals = {
|
|
'name': f'Deposit for PO {self.name}',
|
|
'type': 'service',
|
|
'purchase_ok': True,
|
|
'sale_ok': False,
|
|
'invoice_policy': 'order', # Ordered quantities for deposit
|
|
'supplier_taxes_id': [(6, 0, [])], # No supplier taxes
|
|
}
|
|
deposit_product = self.env['product.product'].create(product_vals)
|
|
self.deposit_product_id = deposit_product.id
|
|
return self.deposit_product_id
|
|
|
|
def button_draft(self):
|
|
res = super().button_draft()
|
|
# Remove deposit lines when resetting to draft
|
|
for order in self:
|
|
deposit_lines = order.order_line.filtered(lambda l: l.is_deposit)
|
|
deposit_lines.unlink()
|
|
return res
|
|
|
|
def _update_deposit_line(self):
|
|
"""Update deposit line based on posted advance payments"""
|
|
self.ensure_one()
|
|
|
|
# Calculate total posted advance payments
|
|
posted_advance_total = sum(
|
|
payment.amount for payment in self.advance_payment_ids.filtered(lambda p: p.state == 'posted')
|
|
)
|
|
|
|
if posted_advance_total <= 0:
|
|
# Remove deposit line if no posted payments
|
|
deposit_lines = self.order_line.filtered(lambda l: l.is_deposit)
|
|
deposit_lines.unlink()
|
|
return
|
|
|
|
# Get or create deposit product
|
|
deposit_product_id = self.env['ir.config_parameter'].sudo().get_param(
|
|
'purchase_advance_payment.deposit_product_id')
|
|
|
|
if not deposit_product_id:
|
|
raise UserError(
|
|
"Please configure a default advance payment product in Purchase settings."
|
|
)
|
|
|
|
deposit_product = self.env['product.product'].browse(int(deposit_product_id))
|
|
|
|
# Check if deposit line already exists
|
|
existing_deposit_line = self.order_line.filtered(lambda l: l.is_deposit)
|
|
|
|
if existing_deposit_line:
|
|
# Update existing deposit line
|
|
existing_deposit_line.write({
|
|
'product_qty': 1,
|
|
'price_unit': -posted_advance_total, # Negative value for deposit
|
|
'taxes_id': [(6, 0, [])], # No taxes
|
|
})
|
|
else:
|
|
# Create new deposit line
|
|
deposit_vals = {
|
|
'order_id': self.id,
|
|
'product_id': deposit_product.id,
|
|
'name': f'Advance payment for {self.name}',
|
|
'product_qty': 1,
|
|
'product_uom': deposit_product.uom_id.id,
|
|
'price_unit': -posted_advance_total, # Negative value for deposit
|
|
'is_deposit': True,
|
|
'date_planned': fields.Datetime.now(),
|
|
'taxes_id': [(6, 0, [])], # No taxes
|
|
}
|
|
self.env['purchase.order.line'].create(deposit_vals)
|
|
|
|
def action_apply_deposit(self):
|
|
"""Apply advance payment as deposit line in the purchase order"""
|
|
self.ensure_one()
|
|
if self.advance_payment_total <= 0:
|
|
raise UserError("No advance payment found for this purchase order.")
|
|
|
|
# Create or update deposit product
|
|
if not self.deposit_product_id:
|
|
self.action_create_deposit_product()
|
|
|
|
# Check if deposit line already exists
|
|
existing_deposit_line = self.order_line.filtered(lambda l: l.is_deposit)
|
|
|
|
if existing_deposit_line:
|
|
# Update existing deposit line
|
|
existing_deposit_line.write({
|
|
'product_qty': 1,
|
|
'price_unit': -self.advance_payment_total, # Negative value for deposit
|
|
'taxes_id': [(6, 0, [])], # No taxes
|
|
})
|
|
else:
|
|
# Create new deposit line
|
|
deposit_vals = {
|
|
'order_id': self.id,
|
|
'product_id': self.deposit_product_id.id,
|
|
'name': f'Deposit payment for PO {self.name}',
|
|
'product_qty': 1,
|
|
'product_uom': self.deposit_product_id.uom_id.id,
|
|
'price_unit': -self.advance_payment_total, # Negative value for deposit
|
|
'is_deposit': True,
|
|
'date_planned': fields.Datetime.now(),
|
|
'taxes_id': [(6, 0, [])], # No taxes
|
|
}
|
|
self.env['purchase.order.line'].create(deposit_vals)
|
|
|
|
return True
|
|
|
|
def action_create_invoice(self):
|
|
"""Override to ensure deposit line is included in vendor bill"""
|
|
# Apply deposit before creating invoice
|
|
for order in self:
|
|
if order.advance_payment_total > 0:
|
|
order.action_apply_deposit()
|
|
|
|
# Call super to create the invoice
|
|
invoices = super().action_create_invoice()
|
|
|
|
# Ensure deposit lines have quantity 1 in the created invoices
|
|
if 'res_id' in invoices and invoices['res_id']:
|
|
# Single invoice
|
|
invoice = self.env['account.move'].browse(invoices['res_id'])
|
|
self._fix_deposit_line_quantities(invoice)
|
|
elif 'domain' in invoices and invoices['domain']:
|
|
# Multiple invoices
|
|
invoice_ids = self.env['account.move'].search(invoices['domain'])
|
|
for invoice in invoice_ids:
|
|
self._fix_deposit_line_quantities(invoice)
|
|
|
|
return invoices
|
|
|
|
def _fix_deposit_line_quantities(self, invoice):
|
|
"""Fix deposit line quantities in the invoice"""
|
|
for line in invoice.invoice_line_ids:
|
|
if line.purchase_line_id and line.purchase_line_id.is_deposit:
|
|
line.write({
|
|
'quantity': 1.0,
|
|
'tax_ids': [(6, 0, [])] # No taxes
|
|
})
|
|
|
|
|
|
class PurchaseOrderLine(models.Model):
|
|
_inherit = 'purchase.order.line'
|
|
|
|
is_deposit = fields.Boolean(
|
|
string='Is Deposit',
|
|
default=False,
|
|
help='Identifies if this line is a deposit payment'
|
|
)
|
|
|
|
def _prepare_account_move_line(self, move=False):
|
|
"""Override to ensure deposit lines have correct quantity in vendor bill"""
|
|
self.ensure_one()
|
|
res = super()._prepare_account_move_line(move)
|
|
|
|
# If this is a deposit line, ensure quantity is 1 and no taxes
|
|
if self.is_deposit:
|
|
res['quantity'] = 1
|
|
res['tax_ids'] = [(6, 0, [])] # No taxes
|
|
|
|
return res
|
|
|
|
def _get_invoice_qty(self):
|
|
"""Override to ensure deposit lines have correct quantity for invoicing"""
|
|
self.ensure_one()
|
|
if self.is_deposit:
|
|
# For deposit lines, always invoice quantity 1 regardless of received qty
|
|
return 1.0
|
|
return super()._get_invoice_qty() |