change the flow of advance payment, now creating advance payment only from purchase order form
This commit is contained in:
parent
137a64686c
commit
ec53246e23
80
README.md
80
README.md
@ -1,25 +1,77 @@
|
|||||||
# Purchase Advance Payment
|
# Purchase Advance Payment
|
||||||
|
|
||||||
This module allows linking payments to purchase orders as advance payments.
|
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
|
## Features
|
||||||
|
|
||||||
- Link payments to purchase orders as advance payments
|
- Create advance payments directly from Purchase Order form
|
||||||
- Automatically subtract advance payments from the total when the PO is fully billed
|
- Automatic deposit line creation in PO
|
||||||
- Create deposit products on the PO so the final invoice includes the deposit product
|
- Proper accounting entries using:
|
||||||
- Track advance payments linked to purchase orders
|
- Expense account from default advance payment product
|
||||||
|
- Outstanding payment account from selected journal
|
||||||
## Usage
|
- Payment remains in draft state for review before posting
|
||||||
|
- View all advance payments linked to a PO
|
||||||
1. Create a purchase order
|
- Deposit line automatically updates when payment is posted
|
||||||
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
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
No additional configuration is required.
|
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.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Creating an Advance Payment
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,26 @@
|
|||||||
{
|
{
|
||||||
'name': 'Purchase Advance Payment',
|
'name': 'Purchase Advance Payment',
|
||||||
'version': '17.0.1.0.0',
|
'version': '17.0.2.0.0',
|
||||||
'category': 'Purchase',
|
'category': 'Purchase',
|
||||||
'summary': 'Link payments to purchase orders as advance payments',
|
'summary': 'Create advance payments directly from purchase orders',
|
||||||
'description': """
|
'description': """
|
||||||
This module allows linking payments to purchase orders as advance payments.
|
This module allows creating advance payments directly from purchase orders.
|
||||||
When a PO is fully billed, the total is subtracted by the advance payment made.
|
When an advance payment is created, a deposit line is automatically added to the PO.
|
||||||
After payment is linked to the PO, a deposit product is created so the final
|
The journal entry uses the expense account from the default advance payment product
|
||||||
invoice/vendor bills will include the deposit 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
|
||||||
""",
|
""",
|
||||||
'author': 'Suherdy Yacob',
|
'author': 'Suherdy Yacob',
|
||||||
'depends': ['purchase', 'account'],
|
'depends': ['purchase', 'account'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/product_data.xml',
|
'data/product_data.xml',
|
||||||
'wizard/link_advance_payment_wizard_views.xml',
|
'wizard/create_advance_payment_wizard_views.xml',
|
||||||
'views/purchase_advance_payment_views.xml',
|
'views/purchase_advance_payment_views.xml',
|
||||||
'views/purchase_order_views.xml',
|
'views/purchase_order_views.xml',
|
||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
|
|||||||
BIN
__pycache__/__init__.cpython-310.pyc
Normal file
BIN
__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/account_payment.cpython-310.pyc
Normal file
BIN
models/__pycache__/account_payment.cpython-310.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/account_payment.cpython-312.pyc
Normal file
BIN
models/__pycache__/account_payment.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/purchase_order.cpython-310.pyc
Normal file
BIN
models/__pycache__/purchase_order.cpython-310.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/purchase_order.cpython-312.pyc
Normal file
BIN
models/__pycache__/purchase_order.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/res_config_settings.cpython-310.pyc
Normal file
BIN
models/__pycache__/res_config_settings.cpython-310.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/res_config_settings.cpython-312.pyc
Normal file
BIN
models/__pycache__/res_config_settings.cpython-312.pyc
Normal file
Binary file not shown.
@ -21,11 +21,49 @@ class AccountPayment(models.Model):
|
|||||||
if self.purchase_order_id:
|
if self.purchase_order_id:
|
||||||
self.amount = self.purchase_order_id.amount_residual
|
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):
|
def action_post(self):
|
||||||
res = super().action_post()
|
res = super().action_post()
|
||||||
# When an advance payment is posted, link it to the purchase order
|
# When an advance payment is posted, update deposit line in purchase order
|
||||||
for payment in self:
|
for payment in self:
|
||||||
if payment.is_advance_payment and payment.purchase_order_id:
|
if payment.is_advance_payment and payment.purchase_order_id:
|
||||||
# Apply the deposit to the purchase order
|
# Update the deposit line with the actual posted amount
|
||||||
payment.purchase_order_id.action_apply_deposit()
|
payment.purchase_order_id._update_deposit_line()
|
||||||
return res
|
return res
|
||||||
@ -87,6 +87,57 @@ class PurchaseOrder(models.Model):
|
|||||||
deposit_lines.unlink()
|
deposit_lines.unlink()
|
||||||
return res
|
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):
|
def action_apply_deposit(self):
|
||||||
"""Apply advance payment as deposit line in the purchase order"""
|
"""Apply advance payment as deposit line in the purchase order"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
access_link_advance_payment_wizard,access.link.advance.payment.wizard,model_link_advance_payment_wizard,base.group_user,1,1,1,1
|
access_create_advance_payment_wizard,access.create.advance.payment.wizard,model_create_advance_payment_wizard,base.group_user,1,1,1,1
|
||||||
|
@ -15,13 +15,7 @@
|
|||||||
required="is_advance_payment"/>
|
required="is_advance_payment"/>
|
||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//header" position="inside">
|
<!-- Removed Link to PO button as per requirement -->
|
||||||
<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>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|||||||
@ -15,24 +15,21 @@
|
|||||||
|
|
||||||
<xpath expr="//sheet/notebook" position="inside">
|
<xpath expr="//sheet/notebook" position="inside">
|
||||||
<page string="Advance Payments" name="advance_payments">
|
<page string="Advance Payments" name="advance_payments">
|
||||||
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="advance_payment_total" widget="monetary"/>
|
<field name="advance_payment_total" widget="monetary"/>
|
||||||
<field name="amount_residual" widget="monetary"/>
|
<field name="amount_residual" widget="monetary"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<button name="action_apply_deposit"
|
<button name="%(purchase_advance_payment.action_create_advance_payment_wizard)d"
|
||||||
string="Apply Deposit"
|
string="Create Advance Payment"
|
||||||
type="object"
|
|
||||||
class="btn-primary"
|
|
||||||
invisible="advance_payment_total <= 0"/>
|
|
||||||
<button name="%(purchase_advance_payment.action_link_advance_payment_wizard)d"
|
|
||||||
string="Link Advance Payment"
|
|
||||||
type="action"
|
type="action"
|
||||||
class="btn-secondary"
|
class="btn-primary"
|
||||||
context="{'default_purchase_order_id': active_id}"/>
|
context="{'default_purchase_order_id': active_id}"/>
|
||||||
</group>
|
</group>
|
||||||
<field name="advance_payment_ids">
|
</group>
|
||||||
<tree>
|
<field name="advance_payment_ids" nolabel="1">
|
||||||
|
<tree create="false" delete="false">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="date"/>
|
<field name="date"/>
|
||||||
<field name="amount" widget="monetary"/>
|
<field name="amount" widget="monetary"/>
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
from . import link_advance_payment_wizard
|
from . import create_advance_payment_wizard
|
||||||
BIN
wizard/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
wizard/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
wizard/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
wizard/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
wizard/__pycache__/create_advance_payment_wizard.cpython-312.pyc
Normal file
BIN
wizard/__pycache__/create_advance_payment_wizard.cpython-312.pyc
Normal file
Binary file not shown.
189
wizard/create_advance_payment_wizard.py
Normal file
189
wizard/create_advance_payment_wizard.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
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'])]
|
||||||
|
)
|
||||||
|
|
||||||
|
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('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"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create payment
|
||||||
|
payment_vals = {
|
||||||
|
'payment_type': 'outbound',
|
||||||
|
'partner_type': 'supplier',
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'amount': self.amount,
|
||||||
|
'currency_id': self.currency_id.id,
|
||||||
|
'date': self.date,
|
||||||
|
'journal_id': self.journal_id.id,
|
||||||
|
'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)
|
||||||
|
|
||||||
|
# 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)
|
||||||
42
wizard/create_advance_payment_wizard_views.xml
Normal file
42
wizard/create_advance_payment_wizard_views.xml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?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"/>
|
||||||
|
</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>
|
||||||
@ -1,100 +0,0 @@
|
|||||||
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._context.get('active_model') == 'purchase.order' and self._context.get('active_id'):
|
|
||||||
po = self.env['purchase.order'].browse(self._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 payment
|
|
||||||
elif self._context.get('active_model') == 'account.payment' and self._context.get('active_id'):
|
|
||||||
payment = self.env['account.payment'].browse(self._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'
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
<?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