purchase_advance_payment/models/purchase_order.py
2025-09-23 14:15:11 +07:00

187 lines
7.0 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 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()