Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7ffb8b16d | |||
| 3328179534 |
78
README.md
78
README.md
@ -1,78 +1,26 @@
|
||||
# Purchase Advance Payment
|
||||
|
||||
This module allows creating advance payments directly from purchase orders.
|
||||
When an advance payment is created, a deposit line is automatically added to the PO.
|
||||
The journal entry uses the expense account from the default advance payment product
|
||||
and the outstanding payment account from the selected cash/bank journal.
|
||||
This module allows linking payments to purchase orders as advance payments.
|
||||
|
||||
## Features
|
||||
|
||||
- Create advance payments directly from Purchase Order form
|
||||
- Automatic deposit line creation in PO
|
||||
- Proper accounting entries using:
|
||||
- Expense account from default advance payment product
|
||||
- Outstanding payment account from selected journal
|
||||
- Payment remains in draft state for review before posting
|
||||
- View all advance payments linked to a PO
|
||||
- Deposit line automatically updates when payment is posted
|
||||
|
||||
## Configuration
|
||||
|
||||
1. **Set Default Advance Payment Product**
|
||||
- Go to **Purchase > Configuration > Settings**
|
||||
- Scroll to **Advance Payment** section
|
||||
- Set the **Default Deposit Product** - this product's expense account will be used for advance payment journal entries
|
||||
|
||||
2. **Configure Outstanding Payment Accounts** (Choose one option)
|
||||
|
||||
**Option A: Journal-Specific** (for multiple banks)
|
||||
- Go to **Accounting > Configuration > Journals**
|
||||
- Open each journal > **Outgoing Payments** tab
|
||||
- Set **Outstanding Payments Account** in payment methods
|
||||
|
||||
**Option B: Company Default** (for single bank)
|
||||
- Go to **Accounting > Configuration > Settings**
|
||||
- Set **Outstanding Payments Account** in Default Accounts section
|
||||
|
||||
Note: Journal-specific accounts take priority over company default.
|
||||
- Link payments to purchase orders as advance payments
|
||||
- Automatically subtract advance payments from the total when the PO is fully billed
|
||||
- Create deposit products on the PO so the final invoice includes the deposit product
|
||||
- Track advance payments linked to purchase orders
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating an Advance Payment
|
||||
1. Create a purchase order
|
||||
2. Create a payment and link it to the purchase order as an advance payment
|
||||
3. When the payment is posted, it will automatically be applied as a deposit to the purchase order
|
||||
4. The deposit will appear as a negative line item on the purchase order
|
||||
5. When creating the vendor bill, the deposit will be included
|
||||
|
||||
1. Open a Purchase Order (must be in confirmed state)
|
||||
2. Go to **Advance Payments** tab
|
||||
3. Click **Create Advance Payment** button
|
||||
4. Fill in the wizard:
|
||||
- **Journal**: Select cash or bank journal
|
||||
- **Amount**: Enter advance payment amount
|
||||
- **Date**: Payment date
|
||||
- **Memo**: Optional description
|
||||
5. Click **Confirm**
|
||||
6. The payment is created in draft state for review
|
||||
7. A deposit line is automatically added to the PO with negative amount
|
||||
8. Review the payment and click **Confirm** to post it
|
||||
## Configuration
|
||||
|
||||
### Journal Entry Accounts
|
||||
|
||||
When the payment is posted, the journal entry will use:
|
||||
- **Debit**: Expense account from the default advance payment product
|
||||
- **Credit**: Outstanding payment account from the selected journal
|
||||
|
||||
### Vendor Bill Creation
|
||||
|
||||
When creating a vendor bill from the PO:
|
||||
1. The deposit line will be included in the bill
|
||||
2. The deposit reduces the total amount payable
|
||||
3. The advance payment is properly accounted for
|
||||
|
||||
## Technical Details
|
||||
|
||||
- Module creates a deposit product line in the PO with negative price
|
||||
- Payment model is extended to handle advance payment accounting
|
||||
- Journal entries are customized to use correct accounts
|
||||
- Deposit line updates automatically when payment is posted
|
||||
No additional configuration is required.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- None
|
||||
- None
|
||||
@ -1,26 +1,20 @@
|
||||
{
|
||||
'name': 'Purchase Advance Payment',
|
||||
'version': '17.0.2.0.0',
|
||||
'version': '19.0.1.0.0',
|
||||
'category': 'Purchase',
|
||||
'summary': 'Create advance payments directly from purchase orders',
|
||||
'summary': 'Link payments to purchase orders as advance payments',
|
||||
'description': """
|
||||
This module allows creating advance payments directly from purchase orders.
|
||||
When an advance payment is created, a deposit line is automatically added to the PO.
|
||||
The journal entry uses the expense account from the default advance payment product
|
||||
and the outstanding payment account from the selected cash/bank journal.
|
||||
|
||||
Features:
|
||||
- Create advance payments from PO form
|
||||
- Automatic deposit line creation
|
||||
- Proper accounting with configurable accounts
|
||||
- Payment remains in draft for review
|
||||
This module allows linking payments to purchase orders as advance payments.
|
||||
When a PO is fully billed, the total is subtracted by the advance payment made.
|
||||
After payment is linked to the PO, a deposit product is created so the final
|
||||
invoice/vendor bills will include the deposit product.
|
||||
""",
|
||||
'author': 'Suherdy Yacob',
|
||||
'depends': ['purchase', 'account'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/product_data.xml',
|
||||
'wizard/create_advance_payment_wizard_views.xml',
|
||||
'wizard/link_advance_payment_wizard_views.xml',
|
||||
'views/purchase_advance_payment_views.xml',
|
||||
'views/purchase_order_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
@ -28,4 +22,4 @@
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -16,75 +16,16 @@ class AccountPayment(models.Model):
|
||||
help='Identifies if this payment is an advance payment for a purchase order'
|
||||
)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
"""Override create to ensure payment_method_line_id is correctly set for advance payments"""
|
||||
for vals in vals_list:
|
||||
# If this is an advance payment and payment_method_line_id is set,
|
||||
# ensure it matches the journal's payment methods
|
||||
if vals.get('is_advance_payment') and vals.get('journal_id') and vals.get('payment_method_line_id'):
|
||||
journal = self.env['account.journal'].browse(vals['journal_id'])
|
||||
payment_method_line = self.env['account.payment.method.line'].browse(vals['payment_method_line_id'])
|
||||
|
||||
# Verify the payment method line belongs to this journal
|
||||
if payment_method_line not in journal.outbound_payment_method_line_ids:
|
||||
# If not, find the correct one
|
||||
correct_method = journal.outbound_payment_method_line_ids.filtered(
|
||||
lambda l: l.payment_method_id == payment_method_line.payment_method_id
|
||||
)[:1]
|
||||
if correct_method:
|
||||
vals['payment_method_line_id'] = correct_method.id
|
||||
|
||||
return super().create(vals_list)
|
||||
|
||||
@api.onchange('purchase_order_id')
|
||||
def _onchange_purchase_order_id(self):
|
||||
if self.purchase_order_id:
|
||||
self.amount = self.purchase_order_id.amount_residual
|
||||
|
||||
def _prepare_move_line_default_vals(self, write_off_line_vals=None, force_balance=None):
|
||||
"""Override to use correct accounts for advance payments"""
|
||||
line_vals_list = super()._prepare_move_line_default_vals(write_off_line_vals=write_off_line_vals, force_balance=force_balance)
|
||||
|
||||
if self.is_advance_payment and self.purchase_order_id:
|
||||
# Get expense account from default deposit product
|
||||
deposit_product_id = self.env['ir.config_parameter'].sudo().get_param(
|
||||
'purchase_advance_payment.deposit_product_id')
|
||||
|
||||
if deposit_product_id:
|
||||
product = self.env['product.product'].browse(int(deposit_product_id))
|
||||
expense_account = product.property_account_expense_id or product.categ_id.property_account_expense_categ_id
|
||||
|
||||
if expense_account:
|
||||
# Get outstanding payment account from journal
|
||||
# For outbound payments, first check journal-specific account, then company default
|
||||
outstanding_account = None
|
||||
|
||||
# Try to get from journal's outbound payment method
|
||||
if self.journal_id.outbound_payment_method_line_ids:
|
||||
outstanding_account = self.journal_id.outbound_payment_method_line_ids[0].payment_account_id
|
||||
|
||||
# Fall back to company default if not set on journal
|
||||
if not outstanding_account:
|
||||
outstanding_account = self.journal_id.company_id.account_journal_payment_credit_account_id
|
||||
|
||||
if outstanding_account:
|
||||
# Modify the line vals to use correct accounts
|
||||
for line_vals in line_vals_list:
|
||||
# The debit line (expense) - partner line
|
||||
if line_vals.get('debit', 0) > 0 and line_vals.get('partner_id'):
|
||||
line_vals['account_id'] = expense_account.id
|
||||
# The credit line (outstanding payment)
|
||||
elif line_vals.get('credit', 0) > 0:
|
||||
line_vals['account_id'] = outstanding_account.id
|
||||
|
||||
return line_vals_list
|
||||
|
||||
def action_post(self):
|
||||
res = super().action_post()
|
||||
# When an advance payment is posted, update deposit line in purchase order
|
||||
# When an advance payment is posted, link it to the purchase order
|
||||
for payment in self:
|
||||
if payment.is_advance_payment and payment.purchase_order_id:
|
||||
# Update the deposit line with the actual posted amount
|
||||
payment.purchase_order_id._update_deposit_line()
|
||||
# Apply the deposit to the purchase order
|
||||
payment.purchase_order_id.action_apply_deposit()
|
||||
return res
|
||||
@ -87,57 +87,6 @@ class PurchaseOrder(models.Model):
|
||||
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()
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_create_advance_payment_wizard,access.create.advance.payment.wizard,model_create_advance_payment_wizard,base.group_user,1,1,1,1
|
||||
access_link_advance_payment_wizard,access.link.advance.payment.wizard,model_link_advance_payment_wizard,base.group_user,1,1,1,1
|
||||
|
@ -15,17 +15,23 @@
|
||||
required="is_advance_payment"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<!-- Removed Link to PO button as per requirement -->
|
||||
<xpath expr="//header" position="inside">
|
||||
<button name="%(purchase_advance_payment.action_link_advance_payment_wizard_payment)d"
|
||||
string="Link to PO"
|
||||
type="action"
|
||||
class="btn-primary"
|
||||
invisible="state != 'draft' or is_advance_payment"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Account Payment Tree View -->
|
||||
<!-- Account Payment List View -->
|
||||
<record id="view_account_payment_tree_inherit_advance_payment" model="ir.ui.view">
|
||||
<field name="name">account.payment.tree.inherit.advance.payment</field>
|
||||
<field name="model">account.payment</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree/field[@name='partner_id']" position="after">
|
||||
<xpath expr="//list/field[@name='partner_id']" position="after">
|
||||
<field name="purchase_order_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
@ -16,31 +16,34 @@
|
||||
<xpath expr="//sheet/notebook" position="inside">
|
||||
<page string="Advance Payments" name="advance_payments">
|
||||
<group>
|
||||
<group>
|
||||
<field name="advance_payment_total" widget="monetary"/>
|
||||
<field name="amount_residual" widget="monetary"/>
|
||||
</group>
|
||||
<group>
|
||||
<button name="%(purchase_advance_payment.action_create_advance_payment_wizard)d"
|
||||
string="Create Advance Payment"
|
||||
type="action"
|
||||
<button name="action_apply_deposit"
|
||||
string="Apply Deposit"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
context="{'default_purchase_order_id': active_id}"/>
|
||||
invisible="advance_payment_total <= 0"/>
|
||||
<button name="%(purchase_advance_payment.action_link_advance_payment_wizard)d"
|
||||
string="Link Advance Payment"
|
||||
type="action"
|
||||
class="btn-secondary"
|
||||
context="{'default_purchase_order_id': id}"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="advance_payment_ids" nolabel="1">
|
||||
<tree create="false" delete="false">
|
||||
<field name="advance_payment_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="date"/>
|
||||
<field name="amount" widget="monetary"/>
|
||||
<field name="state"/>
|
||||
<field name="journal_id"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='order_line']/tree/field[@name='price_unit']" position="after">
|
||||
<xpath expr="//field[@name='order_line']/list/field[@name='price_unit']" position="after">
|
||||
<field name="is_deposit" invisible="1"/>
|
||||
</xpath>
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
from . import create_advance_payment_wizard
|
||||
from . import link_advance_payment_wizard
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
wizard/__pycache__/link_advance_payment_wizard.cpython-310.pyc
Normal file
BIN
wizard/__pycache__/link_advance_payment_wizard.cpython-310.pyc
Normal file
Binary file not shown.
BIN
wizard/__pycache__/link_advance_payment_wizard.cpython-312.pyc
Normal file
BIN
wizard/__pycache__/link_advance_payment_wizard.cpython-312.pyc
Normal file
Binary file not shown.
@ -1,257 +0,0 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class CreateAdvancePaymentWizard(models.TransientModel):
|
||||
_name = 'create.advance.payment.wizard'
|
||||
_description = 'Create Advance Payment Wizard'
|
||||
|
||||
purchase_order_id = fields.Many2one(
|
||||
'purchase.order',
|
||||
string='Purchase Order',
|
||||
required=True,
|
||||
readonly=True
|
||||
)
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Vendor',
|
||||
related='purchase_order_id.partner_id',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
journal_id = fields.Many2one(
|
||||
'account.journal',
|
||||
string='Journal',
|
||||
required=True,
|
||||
domain=[('type', 'in', ['bank', 'cash'])]
|
||||
)
|
||||
|
||||
payment_method_line_id = fields.Many2one(
|
||||
'account.payment.method.line',
|
||||
string='Payment Method',
|
||||
domain="[('id', 'in', available_payment_method_line_ids)]",
|
||||
help='Manual: Pay by any method outside of Odoo.\n'
|
||||
'Check: Pay by check and print it from Odoo.\n'
|
||||
'Batch Deposit: Collect several checks at once.'
|
||||
)
|
||||
|
||||
available_payment_method_line_ids = fields.Many2many(
|
||||
'account.payment.method.line',
|
||||
compute='_compute_available_payment_method_line_ids'
|
||||
)
|
||||
|
||||
amount = fields.Monetary(
|
||||
string='Amount',
|
||||
required=True,
|
||||
currency_field='currency_id'
|
||||
)
|
||||
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency',
|
||||
string='Currency',
|
||||
related='purchase_order_id.currency_id',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
date = fields.Date(
|
||||
string='Date',
|
||||
required=True,
|
||||
default=fields.Date.context_today
|
||||
)
|
||||
|
||||
memo = fields.Char(
|
||||
string='Memo'
|
||||
)
|
||||
|
||||
expense_account_id = fields.Many2one(
|
||||
'account.account',
|
||||
string='Expense Account',
|
||||
compute='_compute_expense_account',
|
||||
store=True,
|
||||
readonly=True
|
||||
)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
res = super().default_get(fields_list)
|
||||
if self._context.get('default_purchase_order_id'):
|
||||
po = self.env['purchase.order'].browse(self._context['default_purchase_order_id'])
|
||||
res['amount'] = po.amount_residual
|
||||
res['memo'] = f'Advance payment for {po.name}'
|
||||
return res
|
||||
|
||||
@api.depends('journal_id')
|
||||
def _compute_available_payment_method_line_ids(self):
|
||||
"""Compute available payment methods based on selected journal"""
|
||||
for wizard in self:
|
||||
if wizard.journal_id:
|
||||
wizard.available_payment_method_line_ids = wizard.journal_id.outbound_payment_method_line_ids
|
||||
else:
|
||||
wizard.available_payment_method_line_ids = False
|
||||
|
||||
@api.onchange('journal_id')
|
||||
def _onchange_journal_id(self):
|
||||
"""Reset payment method when journal changes"""
|
||||
if self.journal_id:
|
||||
# Auto-select the first available payment method (usually 'manual')
|
||||
available_methods = self.journal_id.outbound_payment_method_line_ids
|
||||
if available_methods:
|
||||
# Prefer 'manual' payment method
|
||||
manual_method = available_methods.filtered(
|
||||
lambda l: l.payment_method_id.code == 'manual'
|
||||
)[:1]
|
||||
self.payment_method_line_id = manual_method or available_methods[:1]
|
||||
else:
|
||||
self.payment_method_line_id = False
|
||||
else:
|
||||
self.payment_method_line_id = False
|
||||
|
||||
@api.depends('purchase_order_id')
|
||||
def _compute_expense_account(self):
|
||||
"""Get expense account from default advance payment product"""
|
||||
for wizard in self:
|
||||
deposit_product_id = self.env['ir.config_parameter'].sudo().get_param(
|
||||
'purchase_advance_payment.deposit_product_id')
|
||||
if deposit_product_id:
|
||||
product = self.env['product.product'].browse(int(deposit_product_id))
|
||||
# Get expense account from product
|
||||
account = product.property_account_expense_id or product.categ_id.property_account_expense_categ_id
|
||||
wizard.expense_account_id = account.id
|
||||
else:
|
||||
wizard.expense_account_id = False
|
||||
|
||||
def action_create_payment(self):
|
||||
"""Create advance payment with proper journal entries"""
|
||||
self.ensure_one()
|
||||
|
||||
if self.amount <= 0:
|
||||
raise UserError("Amount must be greater than zero.")
|
||||
|
||||
# Get expense account from default deposit product
|
||||
if not self.expense_account_id:
|
||||
raise UserError(
|
||||
"Please configure a default advance payment product in Purchase settings "
|
||||
"with a valid expense account."
|
||||
)
|
||||
|
||||
# Get outstanding payment account from journal
|
||||
# For outbound payments, first check journal-specific account, then company default
|
||||
outstanding_account = None
|
||||
|
||||
# Try to get from journal's outbound payment method
|
||||
if self.journal_id.outbound_payment_method_line_ids:
|
||||
outstanding_account = self.journal_id.outbound_payment_method_line_ids[0].payment_account_id
|
||||
|
||||
# Fall back to company default if not set on journal
|
||||
if not outstanding_account:
|
||||
outstanding_account = self.journal_id.company_id.account_journal_payment_credit_account_id
|
||||
|
||||
if not outstanding_account:
|
||||
raise UserError(
|
||||
f"Please configure an outstanding payment account for journal '{self.journal_id.name}'.\n"
|
||||
f"You can set it in:\n"
|
||||
f"1. Journal level: Accounting > Configuration > Journals > {self.journal_id.name} > "
|
||||
f"Outgoing Payments tab > Payment Method > Outstanding Payments Account\n"
|
||||
f"OR\n"
|
||||
f"2. Company level: Accounting > Configuration > Settings > Default Accounts > "
|
||||
f"Outstanding Payments Account"
|
||||
)
|
||||
|
||||
# Use the selected payment method line or get the appropriate one
|
||||
payment_method_line = self.payment_method_line_id
|
||||
|
||||
if not payment_method_line:
|
||||
# Fallback: try to get manual payment method
|
||||
payment_method_line = self.journal_id.outbound_payment_method_line_ids.filtered(
|
||||
lambda l: l.payment_method_id.code == 'manual'
|
||||
)[:1]
|
||||
|
||||
if not payment_method_line:
|
||||
# Fallback to first available outbound payment method
|
||||
payment_method_line = self.journal_id.outbound_payment_method_line_ids[:1]
|
||||
|
||||
if not payment_method_line:
|
||||
raise UserError(
|
||||
f"No outbound payment method is configured for journal '{self.journal_id.name}'.\n"
|
||||
f"Please configure a payment method in: Accounting > Configuration > Journals > "
|
||||
f"{self.journal_id.name} > Outgoing Payments tab"
|
||||
)
|
||||
|
||||
# Create payment
|
||||
# CRITICAL: Set payment_type and partner_type FIRST, then journal_id, then payment_method_line_id
|
||||
# This ensures Odoo's onchange logic correctly filters and sets the payment method
|
||||
payment_vals = {
|
||||
'payment_type': 'outbound',
|
||||
'partner_type': 'supplier',
|
||||
'partner_id': self.partner_id.id,
|
||||
'journal_id': self.journal_id.id,
|
||||
'payment_method_line_id': payment_method_line.id,
|
||||
'amount': self.amount,
|
||||
'currency_id': self.currency_id.id,
|
||||
'date': self.date,
|
||||
'ref': self.memo or f'Advance payment for {self.purchase_order_id.name}',
|
||||
'purchase_order_id': self.purchase_order_id.id,
|
||||
'is_advance_payment': True,
|
||||
}
|
||||
|
||||
payment = self.env['account.payment'].create(payment_vals)
|
||||
|
||||
# Force recompute of payment method line to ensure it's correctly set
|
||||
# This is necessary because Odoo might have changed it during create
|
||||
if payment.payment_method_line_id != payment_method_line:
|
||||
payment.write({'payment_method_line_id': payment_method_line.id})
|
||||
|
||||
# Create deposit line in purchase order immediately
|
||||
self._create_deposit_line()
|
||||
|
||||
# Return action to open the created payment
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Advance Payment',
|
||||
'res_model': 'account.payment',
|
||||
'res_id': payment.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
def _create_deposit_line(self):
|
||||
"""Create deposit line in purchase order"""
|
||||
# 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.purchase_order_id.order_line.filtered(lambda l: l.is_deposit)
|
||||
|
||||
# Calculate new total advance payment
|
||||
new_advance_total = self.purchase_order_id.advance_payment_total + self.amount
|
||||
|
||||
if existing_deposit_line:
|
||||
# Update existing deposit line
|
||||
existing_deposit_line.write({
|
||||
'product_qty': 1,
|
||||
'price_unit': -new_advance_total,
|
||||
'taxes_id': [(6, 0, [])],
|
||||
})
|
||||
else:
|
||||
# Create new deposit line
|
||||
deposit_vals = {
|
||||
'order_id': self.purchase_order_id.id,
|
||||
'product_id': deposit_product.id,
|
||||
'name': f'Advance payment for {self.purchase_order_id.name}',
|
||||
'product_qty': 1,
|
||||
'product_uom': deposit_product.uom_id.id,
|
||||
'price_unit': -self.amount,
|
||||
'is_deposit': True,
|
||||
'date_planned': fields.Datetime.now(),
|
||||
'taxes_id': [(6, 0, [])],
|
||||
}
|
||||
self.env['purchase.order.line'].create(deposit_vals)
|
||||
@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Create Advance Payment Wizard Form View -->
|
||||
<record id="view_create_advance_payment_wizard_form" model="ir.ui.view">
|
||||
<field name="name">create.advance.payment.wizard.form</field>
|
||||
<field name="model">create.advance.payment.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create Advance Payment">
|
||||
<group>
|
||||
<group>
|
||||
<field name="purchase_order_id" readonly="1"/>
|
||||
<field name="partner_id" readonly="1"/>
|
||||
<field name="journal_id" required="1"/>
|
||||
<field name="available_payment_method_line_ids" invisible="1"/>
|
||||
<field name="payment_method_line_id" required="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="amount" required="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="date" required="1"/>
|
||||
<field name="memo"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="expense_account_id" readonly="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Confirm" name="action_create_payment" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Create Advance Payment Wizard Action -->
|
||||
<record id="action_create_advance_payment_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Create Advance Payment</field>
|
||||
<field name="res_model">create.advance.payment.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
107
wizard/link_advance_payment_wizard.py
Normal file
107
wizard/link_advance_payment_wizard.py
Normal file
@ -0,0 +1,107 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class LinkAdvancePaymentWizard(models.TransientModel):
|
||||
_name = 'link.advance.payment.wizard'
|
||||
_description = 'Link Advance Payment Wizard'
|
||||
|
||||
purchase_order_id = fields.Many2one(
|
||||
'purchase.order',
|
||||
string='Purchase Order'
|
||||
)
|
||||
|
||||
payment_id = fields.Many2one(
|
||||
'account.payment',
|
||||
string='Payment',
|
||||
domain=[('state', '=', 'draft')]
|
||||
)
|
||||
|
||||
amount = fields.Monetary(
|
||||
string='Amount'
|
||||
)
|
||||
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency',
|
||||
string='Currency'
|
||||
)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super().default_get(fields)
|
||||
# Handle context from purchase order
|
||||
if self.env.context.get('active_model') == 'purchase.order' and self.env.context.get('active_id'):
|
||||
po = self.env['purchase.order'].browse(self.env.context['active_id'])
|
||||
res['purchase_order_id'] = po.id
|
||||
res['currency_id'] = po.currency_id.id
|
||||
if 'amount' in fields:
|
||||
res['amount'] = po.amount_residual
|
||||
# Handle context from purchase order when called from the button (using default_purchase_order_id)
|
||||
elif self.env.context.get('default_purchase_order_id'):
|
||||
po = self.env['purchase.order'].browse(self.env.context['default_purchase_order_id'])
|
||||
res['purchase_order_id'] = po.id
|
||||
res['currency_id'] = po.currency_id.id
|
||||
if 'amount' in fields and 'amount' not in res:
|
||||
res['amount'] = po.amount_residual
|
||||
# Handle context from payment
|
||||
elif self.env.context.get('active_model') == 'account.payment' and self.env.context.get('active_id'):
|
||||
payment = self.env['account.payment'].browse(self.env.context['active_id'])
|
||||
res['payment_id'] = payment.id
|
||||
res['currency_id'] = payment.currency_id.id
|
||||
if 'amount' in fields:
|
||||
res['amount'] = payment.amount
|
||||
return res
|
||||
|
||||
@api.onchange('payment_id')
|
||||
def _onchange_payment_id(self):
|
||||
if self.payment_id:
|
||||
self.currency_id = self.payment_id.currency_id.id
|
||||
self.amount = self.payment_id.amount
|
||||
|
||||
# Filter purchase orders by partner when payment is selected
|
||||
if self.payment_id.partner_id:
|
||||
purchase_orders = self.env['purchase.order'].search([
|
||||
('partner_id', '=', self.payment_id.partner_id.id),
|
||||
('state', 'in', ['purchase', 'done'])
|
||||
])
|
||||
return {'domain': {'purchase_order_id': [('id', 'in', purchase_orders.ids)]}}
|
||||
return {'domain': {'purchase_order_id': []}}
|
||||
|
||||
@api.onchange('purchase_order_id')
|
||||
def _onchange_purchase_order_id(self):
|
||||
if self.purchase_order_id:
|
||||
self.currency_id = self.purchase_order_id.currency_id.id
|
||||
self.amount = self.purchase_order_id.amount_residual
|
||||
|
||||
# Filter payments by partner when purchase order is selected
|
||||
if self.purchase_order_id.partner_id:
|
||||
payments = self.env['account.payment'].search([
|
||||
('partner_id', '=', self.purchase_order_id.partner_id.id),
|
||||
('state', '=', 'draft'),
|
||||
('payment_type', '=', 'outbound')
|
||||
])
|
||||
return {'domain': {'payment_id': [('id', 'in', payments.ids)]}}
|
||||
return {'domain': {'payment_id': []}}
|
||||
|
||||
def action_link_payment(self):
|
||||
self.ensure_one()
|
||||
if self.amount <= 0:
|
||||
raise UserError("Amount must be greater than zero.")
|
||||
|
||||
# Check if we have both payment and purchase order
|
||||
if not self.payment_id:
|
||||
raise UserError("Payment must be specified.")
|
||||
|
||||
if not self.purchase_order_id:
|
||||
raise UserError("Purchase order must be specified.")
|
||||
|
||||
# Link the payment to the purchase order
|
||||
self.payment_id.write({
|
||||
'purchase_order_id': self.purchase_order_id.id,
|
||||
'is_advance_payment': True,
|
||||
'amount': self.amount
|
||||
})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window_close'
|
||||
}
|
||||
41
wizard/link_advance_payment_wizard_views.xml
Normal file
41
wizard/link_advance_payment_wizard_views.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Link Advance Payment Wizard Form View -->
|
||||
<record id="view_link_advance_payment_wizard_form" model="ir.ui.view">
|
||||
<field name="name">link.advance.payment.wizard.form</field>
|
||||
<field name="model">link.advance.payment.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Link Advance Payment">
|
||||
<group>
|
||||
<field name="purchase_order_id"/>
|
||||
<field name="payment_id" domain="[('state', '=', 'draft'), ('payment_type', '=', 'outbound')]"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Link Payment" name="action_link_payment" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Link Advance Payment Wizard Action -->
|
||||
<record id="action_link_advance_payment_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Link Advance Payment</field>
|
||||
<field name="res_model">link.advance.payment.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- Link Advance Payment Wizard Action from Payment -->
|
||||
<record id="action_link_advance_payment_wizard_payment" model="ir.actions.act_window">
|
||||
<field name="name">Link to Purchase Order</field>
|
||||
<field name="res_model">link.advance.payment.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'active_model': 'account.payment', 'active_id': active_id}</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue
Block a user