feat: implement expense realization payment tracking and automated balancing journal entries
This commit is contained in:
parent
82b36c3ef5
commit
83ea721aa8
@ -30,6 +30,12 @@ This module enhances Odoo's standard Expense workflow by providing account-split
|
|||||||
- **Overdue Tracking**: Automatically calculates and highlights overdue receipts for Kasbon realizations.
|
- **Overdue Tracking**: Automatically calculates and highlights overdue receipts for Kasbon realizations.
|
||||||
- **Simplified UI**: Standard "Split Expense" and other distracting buttons are hidden in the backend to maintain a focused workflow.
|
- **Simplified UI**: Standard "Split Expense" and other distracting buttons are hidden in the backend to maintain a focused workflow.
|
||||||
|
|
||||||
|
### 6. Realization Accounting Logic
|
||||||
|
- **Automated Clearing**: Automatically balances the difference between the original advance (Kasbon) and actual receipts using a **Clearing Account (218401)**.
|
||||||
|
- **Scenario 1 (Spent > Paid)**: Records the extra amount as a credit in the clearing account (Liability to employee).
|
||||||
|
- **Scenario 2 (Spent < Paid)**: Records the remaining balance as a debit in the clearing account (Employee owes back).
|
||||||
|
- **Dynamic Discovery**: Attempts to use the exact bank/outstanding account from the original payment for seamless reconciliation.
|
||||||
|
|
||||||
## 🛠 Configuration
|
## 🛠 Configuration
|
||||||
|
|
||||||
1. **GL Accounts**:
|
1. **GL Accounts**:
|
||||||
|
|||||||
@ -8,5 +8,12 @@
|
|||||||
<field name="padding">5</field>
|
<field name="padding">5</field>
|
||||||
<field name="company_id" eval="False"/>
|
<field name="company_id" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="seq_hr_expense" model="ir.sequence">
|
||||||
|
<field name="name">Expense</field>
|
||||||
|
<field name="code">hr.expense.sequence</field>
|
||||||
|
<field name="prefix">EXP/%(year)s/%(month)s/</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@ -3,5 +3,6 @@ from . import hr_expense
|
|||||||
from . import hr_expense_sheet
|
from . import hr_expense_sheet
|
||||||
from . import account_move_line
|
from . import account_move_line
|
||||||
from . import hr_expense_realization
|
from . import hr_expense_realization
|
||||||
|
from . import account_payment
|
||||||
from . import res_company
|
from . import res_company
|
||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
|
|||||||
6
models/account_payment.py
Normal file
6
models/account_payment.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
class AccountPayment(models.Model):
|
||||||
|
_inherit = 'account.payment'
|
||||||
|
|
||||||
|
realization_id = fields.Many2one('hr.expense.realization', string='Originating Realization', readonly=True)
|
||||||
@ -21,6 +21,15 @@ class HrExpense(models.Model):
|
|||||||
if product.property_account_expense_company_id:
|
if product.property_account_expense_company_id:
|
||||||
expense.account_id = product.property_account_expense_company_id
|
expense.account_id = product.property_account_expense_company_id
|
||||||
|
|
||||||
|
sequence_name = fields.Char(string='Sequence', readonly=True, copy=False, default=lambda self: _('New'))
|
||||||
|
realization_total_amount = fields.Monetary(
|
||||||
|
string='Realization Total',
|
||||||
|
compute='_compute_realization_total_amount',
|
||||||
|
store=True,
|
||||||
|
currency_field='currency_id',
|
||||||
|
help="Total amount from all physical receipts realized for this expense."
|
||||||
|
)
|
||||||
|
|
||||||
receipt_due_date = fields.Date(
|
receipt_due_date = fields.Date(
|
||||||
string="Receipt Due Date",
|
string="Receipt Due Date",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
@ -47,6 +56,18 @@ class HrExpense(models.Model):
|
|||||||
for expense in self:
|
for expense in self:
|
||||||
expense.realization_count = len(expense.realization_ids)
|
expense.realization_count = len(expense.realization_ids)
|
||||||
|
|
||||||
|
@api.depends('realization_ids.total_amount')
|
||||||
|
def _compute_realization_total_amount(self):
|
||||||
|
for expense in self:
|
||||||
|
expense.realization_total_amount = sum(expense.realization_ids.mapped('total_amount'))
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
for vals in vals_list:
|
||||||
|
if vals.get('sequence_name', _('New')) == _('New'):
|
||||||
|
vals['sequence_name'] = self.env['ir.sequence'].next_by_code('hr.expense.sequence') or _('New')
|
||||||
|
return super().create(vals_list)
|
||||||
|
|
||||||
def action_create_realization(self):
|
def action_create_realization(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
if self.payment_mode != 'company_account':
|
if self.payment_mode != 'company_account':
|
||||||
|
|||||||
@ -39,6 +39,41 @@ class HrExpenseRealization(models.Model):
|
|||||||
for rec in self:
|
for rec in self:
|
||||||
rec.total_amount = sum(rec.line_ids.mapped('amount'))
|
rec.total_amount = sum(rec.line_ids.mapped('amount'))
|
||||||
|
|
||||||
|
# Payment tracking
|
||||||
|
discrepancy_amount = fields.Monetary(string='Discrepancy Amount', compute='_compute_discrepancy', store=True)
|
||||||
|
payment_ids = fields.One2many('account.payment', 'realization_id', string='Payments')
|
||||||
|
payment_count = fields.Integer(compute='_compute_payment_count')
|
||||||
|
show_vendor_payment_btn = fields.Boolean(compute='_compute_button_visibility')
|
||||||
|
show_customer_payment_btn = fields.Boolean(compute='_compute_button_visibility')
|
||||||
|
|
||||||
|
@api.depends('move_id.line_ids.amount_currency', 'state', 'payment_ids', 'payment_ids.state')
|
||||||
|
def _compute_discrepancy(self):
|
||||||
|
""" Calculate discrepancy based on balancing accounts (114101/216109) in move_id. """
|
||||||
|
for rec in self:
|
||||||
|
if rec.state != 'posted' or not rec.move_id:
|
||||||
|
rec.discrepancy_amount = 0.0
|
||||||
|
continue
|
||||||
|
|
||||||
|
balancing_lines = rec.move_id.line_ids.filtered(lambda l: l.account_id.code in ('114101', '216109'))
|
||||||
|
payable_val = sum(balancing_lines.filtered(lambda l: l.account_id.code == '216109').mapped('credit'))
|
||||||
|
receivable_val = sum(balancing_lines.filtered(lambda l: l.account_id.code == '114101').mapped('debit'))
|
||||||
|
|
||||||
|
# positive means we owe employee, negative means employee owes us
|
||||||
|
rec.discrepancy_amount = payable_val - receivable_val
|
||||||
|
|
||||||
|
@api.depends('payment_ids')
|
||||||
|
def _compute_payment_count(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.payment_count = len(rec.payment_ids)
|
||||||
|
|
||||||
|
@api.depends('discrepancy_amount', 'state', 'payment_ids', 'payment_ids.state')
|
||||||
|
def _compute_button_visibility(self):
|
||||||
|
for rec in self:
|
||||||
|
# Only show if posted and a discrepancy exists, and no successful payment exists
|
||||||
|
has_payment = any(p.state not in ('draft', 'cancel') for p in rec.payment_ids)
|
||||||
|
rec.show_vendor_payment_btn = rec.state == 'posted' and rec.discrepancy_amount > 0 and not has_payment
|
||||||
|
rec.show_customer_payment_btn = rec.state == 'posted' and rec.discrepancy_amount < 0 and not has_payment
|
||||||
|
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
for vals in vals_list:
|
for vals in vals_list:
|
||||||
@ -73,53 +108,151 @@ class HrExpenseRealization(models.Model):
|
|||||||
if not self.journal_id:
|
if not self.journal_id:
|
||||||
raise UserError(_("Please specify the Journal before posting."))
|
raise UserError(_("Please specify the Journal before posting."))
|
||||||
|
|
||||||
# Determine the Expense Account
|
# 1. Determine accounts
|
||||||
|
# Advance Account (Product's expense account, e.g. 118101)
|
||||||
product = self.expense_id.product_id.with_company(self.company_id)
|
product = self.expense_id.product_id.with_company(self.company_id)
|
||||||
expense_account = product.property_account_expense_company_id or product.property_account_expense_id
|
advance_account = product.property_account_expense_company_id or product.property_account_expense_id
|
||||||
if not expense_account:
|
if not advance_account:
|
||||||
expense_account = self.env['ir.property']._get('property_account_expense_categ_id', 'product.category')
|
advance_account = self.env['ir.property']._get('property_account_expense_categ_id', 'product.category')
|
||||||
|
if not advance_account:
|
||||||
|
raise UserError(_("No advance account found for the product or its category."))
|
||||||
|
|
||||||
if not expense_account:
|
# Partner specific accounts (114101 and 216109 fallback)
|
||||||
raise UserError(_("No expense account found for the product or its category."))
|
partner = self.employee_id.sudo().work_contact_id
|
||||||
|
# Biaya yang masih harus dibayar (Payable fallback 216109)
|
||||||
|
payable_account = partner.property_account_payable_id or self.env['account.account'].search([('code', '=', '216109')], limit=1)
|
||||||
|
# Piutang Karyawan (Receivable fallback 114101)
|
||||||
|
receivable_account = partner.property_account_receivable_id or self.env['account.account'].search([('code', '=', '114101')], limit=1)
|
||||||
|
|
||||||
moves = self.env['account.move']
|
# Force use specific accounts if they exist even if partner has defaults
|
||||||
|
karyawan_acc = self.env['account.account'].search([('code', '=', '114101')], limit=1)
|
||||||
|
if karyawan_acc:
|
||||||
|
receivable_account = karyawan_acc
|
||||||
|
biaya_ymh_acc = self.env['account.account'].search([('code', '=', '216109')], limit=1)
|
||||||
|
if biaya_ymh_acc:
|
||||||
|
payable_account = biaya_ymh_acc
|
||||||
|
|
||||||
|
if not payable_account or not receivable_account:
|
||||||
|
raise UserError(_("Specific accounts for Piutang Karyawan (114101) or Biaya Lain ymh dibayar (216109) not found."))
|
||||||
|
|
||||||
|
# 2. Prepare Balanced Move Lines
|
||||||
|
move_lines = []
|
||||||
|
total_realized = 0.0
|
||||||
|
|
||||||
|
# Debits for Actual Expenses
|
||||||
for line in self.line_ids:
|
for line in self.line_ids:
|
||||||
if not line.counterpart_account_id:
|
if not line.counterpart_account_id:
|
||||||
raise UserError(_("Please specify a Counterpart Account for the receipt: %s") % line.description)
|
raise UserError(_("Please specify a Counterpart Account for the receipt: %s") % line.description)
|
||||||
|
|
||||||
move_vals = {
|
move_lines.append(Command.create({
|
||||||
'journal_id': self.journal_id.id,
|
'name': f"Realization Expense: {line.description}",
|
||||||
'date': self.date,
|
'account_id': line.counterpart_account_id.id,
|
||||||
'ref': f"Realization: {self.expense_id.name} - {line.description}",
|
'debit': line.amount,
|
||||||
'move_type': 'entry',
|
'credit': 0.0,
|
||||||
'line_ids': [
|
'partner_id': self.employee_id.sudo().work_contact_id.id,
|
||||||
Command.create({
|
}))
|
||||||
'name': f"Realization: {self.expense_id.name} ({line.description})",
|
total_realized += line.amount
|
||||||
'account_id': expense_account.id,
|
|
||||||
'debit': 0.0,
|
|
||||||
'credit': line.amount,
|
|
||||||
'partner_id': self.employee_id.sudo().work_contact_id.id,
|
|
||||||
'expense_id': self.expense_id.id,
|
|
||||||
}),
|
|
||||||
Command.create({
|
|
||||||
'name': f"Realization Counterpart: {line.description}",
|
|
||||||
'account_id': line.counterpart_account_id.id,
|
|
||||||
'debit': line.amount,
|
|
||||||
'credit': 0.0,
|
|
||||||
'partner_id': self.employee_id.sudo().work_contact_id.id,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
move = self.env['account.move'].create(move_vals)
|
|
||||||
move.action_post()
|
|
||||||
line.move_id = move.id
|
|
||||||
moves |= move
|
|
||||||
|
|
||||||
|
# Credit for Advance clearing
|
||||||
|
# We clear the remaining advance amount in the first realization for an expense.
|
||||||
|
original_paid = self.expense_id.total_amount
|
||||||
|
|
||||||
|
# Find already cleared advance amount from previous posted moves targeting this expense and advance account
|
||||||
|
cleared_before = sum(self.env['account.move.line'].search([
|
||||||
|
('expense_id', '=', self.expense_id.id),
|
||||||
|
('account_id', '=', advance_account.id),
|
||||||
|
('move_id.state', '=', 'posted')
|
||||||
|
]).mapped('credit'))
|
||||||
|
|
||||||
|
rem_advance = max(0.0, original_paid - cleared_before)
|
||||||
|
|
||||||
|
move_lines.append(Command.create({
|
||||||
|
'name': f"Realization Clear Advance: {self.expense_id.sequence_name}",
|
||||||
|
'account_id': advance_account.id,
|
||||||
|
'debit': 0.0,
|
||||||
|
'credit': rem_advance,
|
||||||
|
'partner_id': self.employee_id.sudo().work_contact_id.id,
|
||||||
|
'expense_id': self.expense_id.id,
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Balancing line based on discrepency
|
||||||
|
diff = total_realized - rem_advance
|
||||||
|
if diff > 0:
|
||||||
|
# Under-realized (Spent more than advance) -> Credit Partner Payable (216109)
|
||||||
|
move_lines.append(Command.create({
|
||||||
|
'name': f"Realization: Biaya yang masih harus dibayar",
|
||||||
|
'account_id': payable_account.id,
|
||||||
|
'debit': 0.0,
|
||||||
|
'credit': diff,
|
||||||
|
'partner_id': self.employee_id.sudo().work_contact_id.id,
|
||||||
|
}))
|
||||||
|
elif diff < 0:
|
||||||
|
# Over-realized (Spent less than advance) -> Debit Partner Receivable (114101)
|
||||||
|
move_lines.append(Command.create({
|
||||||
|
'name': f"Realization: Piutang karyawan",
|
||||||
|
'account_id': receivable_account.id,
|
||||||
|
'debit': abs(diff),
|
||||||
|
'credit': 0.0,
|
||||||
|
'partner_id': self.employee_id.sudo().work_contact_id.id,
|
||||||
|
}))
|
||||||
|
|
||||||
|
move_vals = {
|
||||||
|
'journal_id': self.journal_id.id,
|
||||||
|
'date': self.date,
|
||||||
|
'ref': f"Realization: {self.expense_id.sequence_name} ({self.name})",
|
||||||
|
'move_type': 'entry',
|
||||||
|
'line_ids': move_lines,
|
||||||
|
}
|
||||||
|
|
||||||
|
move = self.env['account.move'].create(move_vals)
|
||||||
|
move.action_post()
|
||||||
|
|
||||||
|
# Link the move to lines and record
|
||||||
|
for line in self.line_ids:
|
||||||
|
line.move_id = move.id
|
||||||
|
|
||||||
self.write({
|
self.write({
|
||||||
'state': 'posted',
|
'state': 'posted',
|
||||||
'move_id': moves[0].id if moves else False
|
'move_id': move.id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def action_create_vendor_payment(self):
|
||||||
|
return self._action_create_payment('outbound')
|
||||||
|
|
||||||
|
def action_create_customer_payment(self):
|
||||||
|
return self._action_create_payment('inbound')
|
||||||
|
|
||||||
|
def _action_create_payment(self, payment_type):
|
||||||
|
""" Opens the account.payment creation form with pre-filled realization data. """
|
||||||
|
self.ensure_one()
|
||||||
|
partner = self.employee_id.sudo().work_contact_id
|
||||||
|
if not partner:
|
||||||
|
raise UserError(_("Employee must have a work contact to register a payment."))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'name': _('Register Payment'),
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'account.payment',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
'context': {
|
||||||
|
'default_payment_type': payment_type,
|
||||||
|
'default_partner_id': partner.id,
|
||||||
|
'default_partner_type': 'supplier' if payment_type == 'outbound' else 'customer',
|
||||||
|
'default_amount': abs(self.discrepancy_amount),
|
||||||
|
'default_ref': f"Realization Balance: {self.name}",
|
||||||
|
'default_realization_id': self.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_view_payments(self):
|
||||||
|
""" Stat button action to view related payments. """
|
||||||
|
self.ensure_one()
|
||||||
|
action = self.env["ir.actions.actions"]._for_xml_id("account.action_account_payments")
|
||||||
|
action['domain'] = [('realization_id', '=', self.id)]
|
||||||
|
action['context'] = {'default_realization_id': self.id}
|
||||||
|
return action
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_pending_realizations(self, employee_id):
|
def get_pending_realizations(self, employee_id):
|
||||||
""" Returns expenses for the given employee that are reported/done but NOT yet realized. """
|
""" Returns expenses for the given employee that are reported/done but NOT yet realized. """
|
||||||
|
|||||||
@ -26,6 +26,86 @@ class HrExpenseSheet(models.Model):
|
|||||||
('none', 'No Receipt Required')
|
('none', 'No Receipt Required')
|
||||||
], string='Receipt Status', compute='_compute_receipt_status', store=True, tracking=True)
|
], string='Receipt Status', compute='_compute_receipt_status', store=True, tracking=True)
|
||||||
|
|
||||||
|
realization_total_amount = fields.Monetary(
|
||||||
|
string='Realization Total',
|
||||||
|
compute='_compute_realization_total_amount',
|
||||||
|
currency_field='currency_id',
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
expense_sequences = fields.Char(
|
||||||
|
string='Expense Sequences',
|
||||||
|
compute='_compute_expense_sequences',
|
||||||
|
store=True,
|
||||||
|
help="Concatenated sequences of all expenses in this report."
|
||||||
|
)
|
||||||
|
|
||||||
|
amount_paid = fields.Monetary(
|
||||||
|
string='Payment Total',
|
||||||
|
compute='_compute_amount_paid',
|
||||||
|
currency_field='currency_id',
|
||||||
|
store=True,
|
||||||
|
help="Total amount paid by the finance team (Total - Residual)."
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('account_move_ids.line_ids.matched_debit_ids', 'account_move_ids.line_ids.matched_credit_ids', 'total_amount', 'amount_residual')
|
||||||
|
def _compute_amount_paid(self):
|
||||||
|
for sheet in self:
|
||||||
|
total_paid = 0.0
|
||||||
|
seen_statement_line_ids = set()
|
||||||
|
seen_payment_ids = set()
|
||||||
|
seen_move_line_ids = set()
|
||||||
|
|
||||||
|
# Find all relevant lines of the sheet's moves (Payable or Clearing accounts)
|
||||||
|
reconcilable_lines = sheet.account_move_ids.line_ids.filtered(
|
||||||
|
lambda l: l.account_id.account_type in ('asset_receivable', 'liability_payable') or
|
||||||
|
l.account_id.reconcile
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in reconcilable_lines:
|
||||||
|
# Get the counterpart lines from partial reconciliations
|
||||||
|
partials = line.matched_debit_ids | line.matched_credit_ids
|
||||||
|
for partial in partials:
|
||||||
|
counterpart = partial.debit_move_id if partial.credit_move_id == line else partial.credit_move_id
|
||||||
|
|
||||||
|
# If the counterpart is from a Bank/Cash journal, it's a "Payment"
|
||||||
|
if counterpart.journal_id.type in ('bank', 'cash'):
|
||||||
|
st_line = counterpart.move_id.statement_line_id
|
||||||
|
payment = counterpart.payment_id
|
||||||
|
|
||||||
|
if st_line:
|
||||||
|
if st_line.id not in seen_statement_line_ids:
|
||||||
|
total_paid += abs(st_line.amount)
|
||||||
|
seen_statement_line_ids.add(st_line.id)
|
||||||
|
elif payment:
|
||||||
|
if payment.id not in seen_payment_ids:
|
||||||
|
total_paid += payment.amount
|
||||||
|
seen_payment_ids.add(payment.id)
|
||||||
|
else:
|
||||||
|
# Fallback to the specific move line's balance (absolute)
|
||||||
|
if counterpart.id not in seen_move_line_ids:
|
||||||
|
total_paid += abs(counterpart.balance)
|
||||||
|
seen_move_line_ids.add(counterpart.id)
|
||||||
|
|
||||||
|
# If no bank transactions found but report is clearly paid/partially paid,
|
||||||
|
# fall back to standard calculation for non-bank flows (e.g. manual journal reconciliation)
|
||||||
|
if not total_paid and sheet.total_amount != sheet.amount_residual:
|
||||||
|
total_paid = sheet.total_amount - sheet.amount_residual
|
||||||
|
|
||||||
|
sheet.amount_paid = total_paid
|
||||||
|
|
||||||
|
@api.depends('expense_line_ids.realization_total_amount')
|
||||||
|
def _compute_realization_total_amount(self):
|
||||||
|
for sheet in self:
|
||||||
|
sheet.realization_total_amount = sum(sheet.expense_line_ids.mapped('realization_total_amount'))
|
||||||
|
|
||||||
|
@api.depends('expense_line_ids.sequence_name')
|
||||||
|
def _compute_expense_sequences(self):
|
||||||
|
for sheet in self:
|
||||||
|
sequences = sheet.expense_line_ids.mapped('sequence_name')
|
||||||
|
# Filter out false values and joins them
|
||||||
|
sheet.expense_sequences = ", ".join(filter(None, sequences))
|
||||||
|
|
||||||
@api.depends('expense_line_ids.receipt_received', 'expense_line_ids.payment_mode')
|
@api.depends('expense_line_ids.receipt_received', 'expense_line_ids.payment_mode')
|
||||||
def _compute_receipt_status(self):
|
def _compute_receipt_status(self):
|
||||||
for sheet in self:
|
for sheet in self:
|
||||||
|
|||||||
@ -25,9 +25,31 @@
|
|||||||
<header>
|
<header>
|
||||||
<button name="action_confirm" string="Confirm" type="object" class="oe_highlight" invisible="state != 'draft'"/>
|
<button name="action_confirm" string="Confirm" type="object" class="oe_highlight" invisible="state != 'draft'"/>
|
||||||
<button name="action_post" string="Post Journal" type="object" class="oe_highlight" groups="account.group_account_invoice" invisible="state != 'confirmed'"/>
|
<button name="action_post" string="Post Journal" type="object" class="oe_highlight" groups="account.group_account_invoice" invisible="state != 'confirmed'"/>
|
||||||
|
|
||||||
|
<!-- Discrepancy Buttons -->
|
||||||
|
<field name="show_vendor_payment_btn" invisible="1"/>
|
||||||
|
<field name="show_customer_payment_btn" invisible="1"/>
|
||||||
|
<button name="action_create_vendor_payment"
|
||||||
|
string="Create Vendor Payment"
|
||||||
|
type="object"
|
||||||
|
class="oe_highlight"
|
||||||
|
groups="account.group_account_invoice"
|
||||||
|
invisible="not show_vendor_payment_btn"/>
|
||||||
|
<button name="action_create_customer_payment"
|
||||||
|
string="Create Customer Payment"
|
||||||
|
type="object"
|
||||||
|
class="oe_highlight"
|
||||||
|
groups="account.group_account_invoice"
|
||||||
|
invisible="not show_customer_payment_btn"/>
|
||||||
|
|
||||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,posted"/>
|
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,posted"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="action_view_payments" type="object" class="oe_stat_button" icon="fa-money" invisible="payment_count == 0">
|
||||||
|
<field name="payment_count" widget="statinfo" string="Payments"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
<h1>
|
<h1>
|
||||||
<field name="name" readonly="1"/>
|
<field name="name" readonly="1"/>
|
||||||
|
|||||||
@ -51,11 +51,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//sheet" position="before">
|
<xpath expr="//sheet" position="before">
|
||||||
|
<field name="sequence_name" invisible="1"/>
|
||||||
<div class="alert alert-danger mb-2" role="alert" invisible="not receipt_overdue">
|
<div class="alert alert-danger mb-2" role="alert" invisible="not receipt_overdue">
|
||||||
This receipt is <strong>Overdue</strong>! Please submit the original receipt as soon as possible.
|
This receipt is <strong>Overdue</strong>! Please submit the original receipt as soon as possible.
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
<xpath expr="//div[hasclass('oe_title')]" position="inside">
|
||||||
|
<h1>
|
||||||
|
<field name="sequence_name" readonly="1" invisible="not id"/>
|
||||||
|
</h1>
|
||||||
|
</xpath>
|
||||||
<xpath expr="//field[@name='account_id']" position="after">
|
<xpath expr="//field[@name='account_id']" position="after">
|
||||||
|
<field name="realization_total_amount" invisible="payment_mode != 'company_account'"/>
|
||||||
<field name="realization_count" invisible="1"/>
|
<field name="realization_count" invisible="1"/>
|
||||||
<field name="receipt_due_date" widget="date" invisible="not receipt_due_date"/>
|
<field name="receipt_due_date" widget="date" invisible="not receipt_due_date"/>
|
||||||
<field name="receipt_received" widget="boolean_toggle" invisible="not receipt_due_date"/>
|
<field name="receipt_received" widget="boolean_toggle" invisible="not receipt_due_date"/>
|
||||||
@ -70,6 +77,12 @@
|
|||||||
<field name="model">hr.expense</field>
|
<field name="model">hr.expense</field>
|
||||||
<field name="inherit_id" ref="hr_expense.view_expenses_tree"/>
|
<field name="inherit_id" ref="hr_expense.view_expenses_tree"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='name']" position="before">
|
||||||
|
<field name="sequence_name" optional="show"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='total_amount']" position="after">
|
||||||
|
<field name="realization_total_amount" optional="show" sum="Total Realization" invisible="payment_mode != 'company_account'"/>
|
||||||
|
</xpath>
|
||||||
<xpath expr="//field[@name='state']" position="after">
|
<xpath expr="//field[@name='state']" position="after">
|
||||||
<field name="receipt_due_date" optional="show" widget="remaining_days" decoration-danger="receipt_overdue"/>
|
<field name="receipt_due_date" optional="show" widget="remaining_days" decoration-danger="receipt_overdue"/>
|
||||||
<field name="receipt_received" optional="show" widget="boolean_toggle"/>
|
<field name="receipt_received" optional="show" widget="boolean_toggle"/>
|
||||||
@ -131,6 +144,12 @@
|
|||||||
<attribute name="groups">account.group_account_invoice</attribute>
|
<attribute name="groups">account.group_account_invoice</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//field[@name='expense_line_ids']/tree/field[@name='name']" position="before">
|
||||||
|
<field name="sequence_name" column_invisible="not parent.id"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='expense_line_ids']/tree/field[@name='total_amount']" position="after">
|
||||||
|
<field name="realization_total_amount" optional="show" invisible="payment_mode != 'company_account'"/>
|
||||||
|
</xpath>
|
||||||
<xpath expr="//field[@name='expense_line_ids']/tree/field[@name='name']" position="after">
|
<xpath expr="//field[@name='expense_line_ids']/tree/field[@name='name']" position="after">
|
||||||
<field name="payment_mode" column_invisible="True"/>
|
<field name="payment_mode" column_invisible="True"/>
|
||||||
<field name="realization_count" column_invisible="True"/>
|
<field name="realization_count" column_invisible="True"/>
|
||||||
@ -161,6 +180,9 @@
|
|||||||
<field name="inherit_id" ref="hr_expense.view_hr_expense_sheet_tree"/>
|
<field name="inherit_id" ref="hr_expense.view_hr_expense_sheet_tree"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='state']" position="before">
|
<xpath expr="//field[@name='state']" position="before">
|
||||||
|
<field name="expense_sequences" optional="show"/>
|
||||||
|
<field name="amount_paid" optional="show" sum="Total Payment" string="Payment Total"/>
|
||||||
|
<field name="realization_total_amount" optional="show" sum="Total Realization"/>
|
||||||
<field name="receipt_status" widget="badge"
|
<field name="receipt_status" widget="badge"
|
||||||
decoration-info="receipt_status == 'pending'"
|
decoration-info="receipt_status == 'pending'"
|
||||||
decoration-success="receipt_status == 'received'"
|
decoration-success="receipt_status == 'received'"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user