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()