Skip to content

Instantly share code, notes, and snippets.

@codeagencybe
Created July 15, 2025 00:56
Show Gist options
  • Save codeagencybe/0f68cbbd450c190d02182cdf878aa4b6 to your computer and use it in GitHub Desktop.
Save codeagencybe/0f68cbbd450c190d02182cdf878aa4b6 to your computer and use it in GitHub Desktop.
Convert purchase order to vendor bill -- Odoo 18.3+ workaround
# Convert Purchase Order to Vendor Bill
# Works for single record or multiple records
log(f"Processing {len(records)} records")
for po in records:
log(f"Processing PO: {po.name}, Model: {po._name}, State: {po.state}")
# Skip if not a purchase order
if po._name != 'purchase.order':
log(f"Skipping {po.name} - not a purchase order", level='warning')
continue
# More lenient state check - include 'to approve' state
if po.state not in ['purchase', 'done', 'to approve']:
log(f"PO {po.name} is not in billable state (state: {po.state})", level='warning')
continue
# Check order lines
log(f"PO {po.name} has {len(po.order_line)} order lines")
for line in po.order_line:
log(f"Line: {line.name}, Qty ordered: {line.product_qty}, Qty received: {line.qty_received}, Qty invoiced: {line.qty_invoiced}")
# Less strict - allow billing even without receipts (useful for services)
billable_lines = po.order_line.filtered(lambda l: l.product_qty > 0 and l.qty_invoiced < l.product_qty)
if not billable_lines:
log(f"PO {po.name} has no billable lines", level='warning')
continue
# Create vendor bill
bill_vals = {
'move_type': 'in_invoice',
'partner_id': po.partner_id.id,
'purchase_vendor_bill_id': po.id, # Link to PO
'ref': po.partner_ref or po.name,
'invoice_date': datetime.date.today(),
'currency_id': po.currency_id.id,
'invoice_line_ids': [],
}
# Add invoice lines for all billable products
for line in billable_lines:
# Use received qty if available, otherwise use ordered qty
qty_to_bill = max(line.qty_received, line.product_qty) - line.qty_invoiced
if qty_to_bill <= 0:
continue
invoice_line_vals = {
'product_id': line.product_id.id,
'name': line.name,
'quantity': qty_to_bill,
'price_unit': line.price_unit,
'tax_ids': [(6, 0, line.taxes_id.ids)],
'purchase_line_id': line.id,
'account_id': line.product_id.property_account_expense_id.id or
line.product_id.categ_id.property_account_expense_categ_id.id,
}
bill_vals['invoice_line_ids'].append((0, 0, invoice_line_vals))
# Create the bill
if bill_vals['invoice_line_ids']:
bill = env['account.move'].create(bill_vals)
log(f"Created vendor bill {bill.name} for PO {po.name}")
# Optional: Auto-confirm the bill
# bill.action_post()
else:
log(f"No billable lines found for PO {po.name}", level='warning')
# Optional: Return action to view created bills
if len(records) == 1:
# Single record - open the created bill
bills = env['account.move'].search([
('purchase_vendor_bill_id', '=', records.id),
('state', '=', 'draft')
], limit=1)
if bills:
action = {
'type': 'ir.actions.act_window',
'name': 'Vendor Bill',
'res_model': 'account.move',
'res_id': bills.id,
'view_mode': 'form',
'target': 'current',
}
else:
# Multiple records - show list view of all bills
action = {
'type': 'ir.actions.act_window',
'name': 'Generated Vendor Bills',
'res_model': 'account.move',
'view_mode': 'tree,form',
'domain': [('purchase_vendor_bill_id', 'in', records.ids)],
'target': 'current',
}
@HandsonMatheus
Copy link

For SaaS 18.3:

`import datetime

created_bills = env['account.move']

for po in records:
if po._name != 'purchase.order':
continue

if po.state not in ['purchase', 'done', 'to approve']:
    continue

billable_lines = po.order_line.filtered(lambda l: l.product_qty > 0 and l.qty_invoiced < l.product_qty)

if not billable_lines:
    continue

bill_vals = {
    'move_type': 'in_invoice',
    'partner_id': po.partner_id.id,
    'purchase_id': po.id,
    'ref': po.partner_ref or po.name,
    'invoice_date': datetime.date.today(),
    'currency_id': po.currency_id.id,
    'invoice_line_ids': [],
}

for line in billable_lines:
    qty_to_bill = max(line.qty_received, line.product_qty) - line.qty_invoiced
    
    if qty_to_bill <= 0:
        continue
        
    invoice_line_vals = {
        'product_id': line.product_id.id,
        'name': line.name,
        'quantity': qty_to_bill,
        'price_unit': line.price_unit,
        'tax_ids': [(6, 0, line.tax_ids.ids)],
        'purchase_line_id': line.id,
        'account_id': line.product_id.property_account_expense_id.id or 
                      line.product_id.categ_id.property_account_expense_categ_id.id,
    }
    
    bill_vals['invoice_line_ids'].append((0, 0, invoice_line_vals))

if bill_vals['invoice_line_ids']:
    bill = env['account.move'].create(bill_vals)
    created_bills += bill
else:
    pass 

if len(records) == 1:
if created_bills:
action = {
'type': 'ir.actions.act_window',
'name': 'Vendor Bill',
'res_model': 'account.move',
'res_id': created_bills.id,
'view_mode': 'form',
'target': 'current',
}
else:
action = {'type': 'ir.actions.act_window_close'}
else:
if created_bills:
action = {
'type': 'ir.actions.act_window',
'name': 'Generated Vendor Bills',
'res_model': 'account.move',
'view_mode': 'tree,form',
'domain': [('id', 'in', created_bills.ids)],
'target': 'current',
}
else:
action = {'type': 'ir.actions.act_window_close'}
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment