change employee_custody_request

This commit is contained in:
mohammed-alkhazrji 2025-12-25 00:05:32 +03:00
parent 2961cd55b2
commit 1d0b12d841
17 changed files with 1068 additions and 321 deletions

View File

@ -20,7 +20,7 @@ This course provides a comprehensive, hands-on guide to managing employee custod
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base','hr','account','exp_payroll_custom','hr_expense','odex25_account_reports','system_dashboard_classic','exp_budget_check'],
'depends': ['base','hr','account','exp_payroll_custom','hr_expense','odex25_account_reports','system_dashboard_classic'],
# always loaded
'data': [
@ -33,11 +33,9 @@ This course provides a comprehensive, hands-on guide to managing employee custod
'views/account_journal.xml',
'views/types_custody.xml',
'wizard/account_paymrnt_register_views.xml',
'wizard/hr_request_pledge_confirm_wizard_view.xml',
'views/hr_expenes.xml'
],
# only loaded in demonstration mode
'demo': [
'demo/demo.xml',
],
}

View File

@ -191,7 +191,8 @@ msgstr ""
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.job_request_form_pledge_view
msgid "GM Manager Approve"
msgstr "تصديق المدير العام"
msgstr "تصديق الموارد البشرية"
#. module: employee_custody_request
#: model:ir.actions.server,name:employee_custody_request.action_report_partner_ledger_inherited_test
@ -280,8 +281,9 @@ msgstr ""
#. module: employee_custody_request
#: model:ir.model.fields,field_description:employee_custody_request.field_hr_employee__journal_id
#: model:ir.model.fields,field_description:employee_custody_request.field_hr_request_pledge__journal_id
#: model:ir.model.fields,field_description:employee_custody_request.field_hr_expense_sheet__custody_bank_journal_id
msgid "Journal"
msgstr "دفاتر اليومية"
msgstr "دفتر اليومية"
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.view_employee_journal_form_inherit
@ -405,6 +407,16 @@ msgstr "الدفعات"
msgid "Payroll Officer Approve"
msgstr "تصديق مسؤول الرواتب"
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.job_request_form_pledge_view
msgid "Feed Pledge"
msgstr "تغذية العهدة"
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.job_request_form_pledge_view
msgid "Close Pledge"
msgstr "إقفال العهدة"
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/models.py:0
#, python-format
@ -548,6 +560,13 @@ msgstr "إنتظار الموارد البشرية"
msgid "Wait Transfer"
msgstr "إنتظار الترحيل"
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/models.py:0
#: model:ir.model.fields.selection,name:employee_custody_request.selection__hr_request_pledge__state__locked
#, python-format
msgid "Locked"
msgstr "مغلقة"
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/models.py:0
#: model:ir.model.fields.selection,name:employee_custody_request.selection__hr_request_pledge__state__submit
@ -597,6 +616,7 @@ msgstr "دفتر عهد"
msgid "Custody Partner"
msgstr "شريك العهده"
#. module: employee_custody_request
#: code:addons/odoo/odex25-ensan-project/employee_custody_request/models/models.py:0
#: code:addons/employee_custody_request/models/models.py:0
@ -607,6 +627,7 @@ msgstr "يجب أن يكون لدى الموظف شريك ذو صلة."
#. module: employee_custody_request
#: model:ir.model.fields,field_description:employee_custody_request.field_account_journal__partner_id
#: model:ir.model.fields,field_description:employee_custody_request.field_hr_expense_sheet__custody_partner_id
msgid "Partner"
msgstr "الشريك"
@ -706,4 +727,116 @@ msgstr "عند اختيار نوع الدفع 'عهدة'، يجب تحديد ش
#. module: employee_custody_request
#: model:ir.model.fields.selection,name:employee_custody_request.selection__hr_expense__payment_mode__custody
msgid "Custody"
msgstr "عهده"
msgstr "عهده"
#. module: employee_custody_request
#: model:ir.model.fields,field_description:employee_custody_request.field_custody_types__company_id
#: model:ir.model.fields,field_description:employee_custody_request.field_hr_request_pledge__company_id
#: model:ir.model.fields.selection,name:employee_custody_request.selection__hr_expense__payment_mode__company_account
msgid "Company"
msgstr "المؤسسة"
#. module: employee_custody_request
#: model:ir.model.fields,field_description:employee_custody_request.field_hr_request_pledge__permanent_pledge
msgid "Permanent Pledge"
msgstr "عهدة مستديمة"
#. module: employee_custody_request
#: model:ir.model.fields,field_description:employee_custody_request.field_hr_request_pledge__total_refunded_amount
msgid "Total Refunded Amount"
msgstr "إجمالي قيمة المبالغ المرتجعة"
#. module: employee_custody_request
#: model:ir.model.fields,field_description:employee_custody_request.field_hr_request_pledge__total_paid_amount
msgid "Total Paid Amount"
msgstr "اجمالي المدفوعات"
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/models.py:0
#: code:addons/odoo/preprod_master/odex25_accounting/employee_custody_request/models/models.py:0
#, python-format
msgid "Cannot create a new payment: the last payment for this pledge is less than 1 year old."
msgstr "لا يمكن إنشاء دفعة جديدة: آخر دفعة لهذه العهدة لم يمر عليها عام كامل."
#. module: employee_custody_request
#: model:ir.model.fields,field_description:employee_custody_request.field_account_payment__petty_cash_pledge
#: model:ir.model.fields.selection,name:employee_custody_request.selection__hr_expense__payment_mode__petty_cash
msgid "Petty Cash"
msgstr "عهدة موظف"
#. module: employee_custody_request
#: model:ir.model.fields,field_description:employee_custody_request.field_account_payment__original_move_id
msgid "Reference"
msgstr "المرجع"
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/models.py:0
#: code:addons/odoo/preprod_master/odex25_accounting/employee_custody_request/models/models.py:0
#, python-format
msgid "✅ A new pledge has been successfully created with an amount of %.2f."
msgstr "✅ تم إنشاء عهدة جديدة بنجاح بمبلغ %.2f."
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/models.py:0
#: code:addons/odoo/preprod_master/odex25_accounting/employee_custody_request/models/models.py:0
#, python-format
msgid "✅ The pledge has been successfully funded with an amount of %.2f."
msgstr "✅ تم تغذية العهدة بنجاح بمبلغ %.2f."
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/models.py:0
#: code:addons/odoo/preprod_master/odex25_accounting/employee_custody_request/models/models.py:0
#, python-format
msgid "Operation Successful"
msgstr "تمت العملية بنجاح"
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/hr_expense.py:0
#: code:addons/employee_custody_request/models/hr_expense_sheet.py:0
#, python-format
msgid ""
"The employee %s has no available custody balance to cover this expense "
"sheet."
msgstr "الموظف %s لا يمتلك رصيد عهده متاح لتغطية تقرير المصروف هذا."
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.view_hr_request_pledge_confirm_wizard_form
msgid "Are you sure you want to feed the pledge?"
msgstr "هل أنت متأكد أنك تريد تغذية العهدة؟"
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.view_hr_request_pledge_confirm_wizard_form
msgid "Confirm"
msgstr "تأكيد"
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.view_hr_request_pledge_confirm_wizard_form
msgid "Cancel"
msgstr "إلغاء"
#. module: employee_custody_request
#: code:addons/employee_custody_request/models/models.py:0
#: model:ir.model,name:employee_custody_request.model_hr_request_pledge_confirm_wizard
#, python-format
msgid "Confirmation Wizard for Pledge Payment"
msgstr "التأكيد لدفع العهدة"
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.job_request_form_pledge_view
msgid "Refund"
msgstr "ارجاع"
#. module: employee_custody_request
#: model_terms:ir.ui.view,arch_db:employee_custody_request.job_request_form_pledge_view
msgid "Show Refunds"
msgstr "حساب الارجاع"

View File

@ -17,23 +17,3 @@ class AccountJournalInherit(models.Model):
help='Partner associated with this journal'
)
@api.onchange('custody_journal')
def _onchange_custody_journal(self):
if not self.custody_journal:
return {
'domain': {'default_account_id': []}
}
else:
return {
'domain': {
'default_account_id': [('user_type_id.type', '=', 'receivable')]
}
}
@api.constrains('custody_journal', 'default_account_id')
def _check_custody_account_type(self):
for record in self:
if record.custody_journal and record.default_account_id:
if record.default_account_id.user_type_id.type != 'receivable':
raise ValidationError(_('You must select an account of type "Debit" for the Advances Ledger'))

View File

@ -1,9 +1,43 @@
from odoo import models, _ ,api
from odoo import models, _ ,api,fields
from odoo.exceptions import UserError
class AccountPayment(models.Model):
_inherit = "account.payment"
petty_cash_pledge = fields.Boolean(
string="Petty Cash",
default=False,
)
hr_request_pledge = fields.Many2one('hr.request.pledge')
hr_request_pledge_id = fields.Many2one(
'hr.request.pledge',
string="Pledge",
ondelete="cascade"
)
skip_paired_payment = fields.Boolean(
string="تجاوز إنشاء الدفعة المقابلة",
default=False,
)
original_move_id = fields.Many2one('account.move', string="Reference")
def action_go_to_hr_request_pledge(self):
pledge_record = self.env['hr.request.pledge'].search(
[('id', '=', self.hr_request_pledge_id.id)],
limit=1
)
if pledge_record:
return {
'type': 'ir.actions.act_window',
'name': 'HR Request Pledge',
'res_model': 'hr.request.pledge',
'res_id': pledge_record.id,
'view_mode': 'form',
'view_type': 'form',
'target': 'current',
}
def _synchronize_from_moves(self, changed_fields):
''' Update the account.payment regarding its related account.move.
@ -30,16 +64,26 @@ class AccountPayment(models.Model):
if 'line_ids' in changed_fields:
all_lines = move.line_ids
liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines()
if len(liquidity_lines) != 1 or len(counterpart_lines) != 1:
raise UserError(_(
"The journal entry %s reached an invalid state relative to its payment.\n"
"To be consistent, the journal entry must always contains:\n"
"- one journal item involving the outstanding payment/receipts account.\n"
"- one journal item involving a receivable/payable account.\n"
"- optional journal items, all sharing the same account.\n\n"
) % move.display_name)
if not (
self.move_id
and self.move_id.line_ids
and self.hr_request_pledge_id
and self.is_internal_transfer
):
raise UserError(_(
"The journal entry %s reached an invalid state relative to its payment.\n"
"To be consistent, the journal entry must always contain:\n"
"- one journal item involving the outstanding payment/receipts account.\n"
"- one journal item involving a receivable/payable account.\n"
"- optional journal items, all sharing the same account.\n\n"
) % move.display_name)
if writeoff_lines and len(writeoff_lines.account_id) != 1:
raise UserError(_(
@ -84,12 +128,18 @@ class AccountPayment(models.Model):
'currency_id': liquidity_lines.currency_id.id,
'partner_id': liquidity_lines.partner_id.id,
})
payment_vals_to_write.update({
'amount': abs(liquidity_amount),
'currency_id': liquidity_lines.currency_id.id,
'destination_account_id': counterpart_lines.account_id.id,
'partner_id': liquidity_lines.partner_id.id,
})
if not (
self.move_id
and self.move_id.line_ids
and self.hr_request_pledge_id
and self.is_internal_transfer
):
payment_vals_to_write.update({
'amount': abs(liquidity_amount),
'currency_id': liquidity_lines.currency_id.id,
'destination_account_id': counterpart_lines.account_id.id,
'partner_id': liquidity_lines.partner_id.id,
})
if liquidity_amount > 0.0:
payment_vals_to_write.update({'payment_type': 'inbound'})
elif liquidity_amount < 0.0:
@ -107,27 +157,47 @@ class AccountPayment(models.Model):
is_account_ok = payment.destination_account_id and payment.destination_account_id == payment.journal_id.company_id.transfer_account_id
payment.is_internal_transfer = is_partner_ok and is_account_ok
def action_post(self):
res = super().action_post()
@api.depends('partner_id', 'destination_account_id', 'journal_id', 'skip_paired_payment')
def _compute_is_internal_transfer(self):
super(AccountPayment, self)._compute_is_internal_transfer()
for payment in self:
if not self.hr_request_pledge:
if payment.skip_paired_payment:
payment.is_internal_transfer = True
if payment.journal_id.custody_journal and payment.partner_id:
employee = self.env['hr.employee'].sudo().search([
('user_id.partner_id', '=', payment.partner_id.id)
], limit=1)
print("pledge")
if employee:
self.env['hr.request.pledge'].allocate_payment_to_pledges(
employee_id=employee.id,
journal_id=payment.journal_id.id,
amount=payment.amount
)
@api.model
def create(self, vals):
payment = super(AccountPayment, self).create(vals)
ref = payment.payment_reference
move = self.env['account.move'].search([('name', '=like', f"{ref}%")], limit=1)
if move:
payment.original_move_id = move.id
if move.move_type in ['in_invoice', 'in_receipt'] and move.is_petty_paid:
payment.petty_cash_pledge = True
return payment
def action_draft_accountant(self):
for rec in self:
print(self.move_id.line_ids)
print(self.destination_journal_id)
if self.move_id and self.move_id.line_ids and self.hr_request_pledge_id and self.is_internal_transfer:
for line in self.move_id.line_ids:
if line.debit > 0.0:
line.write({
'account_id': self.destination_journal_id.payment_debit_account_id.id
})
line.move_id._recompute_dynamic_lines()
if rec.petty_cash_pledge and rec.payment_reference:
rec.action_post()
rec.set_posted_state()
else:
print("its hr_request_pledge")
super(AccountPayment, rec).action_draft_accountant()
return True
return res

View File

@ -6,13 +6,45 @@ from odoo.tools import float_compare, float_is_zero
class HrExpense(models.Model):
_inherit = 'hr.expense'
#
# payment_mode = fields.Selection(
# selection_add=[
# ("custody", "Custody")
# ]
# )
payment_mode = fields.Selection([
("own_account", "Employee (to reimburse)"),
("company_account", "Company"),
("custody", "Custody"),
("petty_cash", "Petty Cash")
], default='custody', tracking=True, states={'done': [('readonly', True)], 'approved': [('readonly', True)], 'reported': [('readonly', True)]}, string="Paid By")
payment_mode = fields.Selection(
selection_add=[
("custody", "Custody")
]
)
def action_submit_expenses(self):
for sheet in self:
if sheet.payment_mode == 'custody':
employee = sheet.employee_id
available_pledges = self.env['hr.request.pledge'].sudo().search([
('employee_id', '=', employee.id),
('custody_status', 'in', ['partial', 'new']),
])
available_pledges = available_pledges.filtered(
lambda p: p.custody_status in ['partial', 'new'] and p.state == 'pay'
)
if not available_pledges:
raise UserError(
_("The employee %s has no available custody balance to cover this expense sheet.") % employee.name
)
return super().action_submit_expenses()
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
res = super(HrExpense, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
if view_type in ['form', 'tree']:
if 'fields' in res and 'payment_mode' in res['fields']:
selection = res['fields']['payment_mode']['selection']
# احذف petty_cash من الواجهة
res['fields']['payment_mode']['selection'] = [s for s in selection if s[0] != 'petty_cash']
return res
def _create_sheet_from_expense_custody(self):
"""Create expense sheet for custody mode"""
if any(expense.state != "draft" or expense.sheet_id for expense in self):
@ -32,16 +64,16 @@ class HrExpense(models.Model):
return sheet
def _create_sheet_from_expenses(self):
payment_mode = set(self.mapped("payment_mode"))
if len(payment_mode) > 1 and "petty_cash" in payment_mode:
raise UserError(
_("You cannot create report from many petty cash mode and other.")
)
if all(expense.payment_mode == "petty_cash" for expense in self):
return self._create_sheet_from_expense_petty_cash()
if all(expense.payment_mode == "custody" for expense in self):
return self._create_sheet_from_expense_custody()
return super()._create_sheet_from_expenses()
# payment_mode = set(self.mapped("payment_mode"))
# if len(payment_mode) > 1 and "petty_cash" in payment_mode:
# raise UserError(
# _("You cannot create report from many petty cash mode and other.")
# )
# if all(expense.payment_mode == "petty_cash" for expense in self):
# return self._create_sheet_from_expense_petty_cash()
# if all(expense.payment_mode == "custody" for expense in self):
# return self._create_sheet_from_expense_custody()
return self._create_sheet_from_expense_custody()
def _get_expense_account_destination(self):
self.ensure_one()
@ -57,14 +89,23 @@ class HrExpense(models.Model):
account_dest = self.sheet_id.bank_journal_id.payment_credit_account_id.id
elif self.payment_mode == 'custody':
if self.sheet_id.account_payment_method_id:
account_dest = self.sheet_id.account_payment_method_id.payment_account_id.id
else:
if not self.sheet_id.custody_bank_journal_id.payment_credit_account_id:
raise UserError(
_("No Outstanding Payments Account found for the %s journal, please configure one.") % (
self.sheet_id.custody_bank_journal_id.name))
account_dest = self.sheet_id.custody_bank_journal_id.payment_credit_account_id.id
partner = self.sheet_id.custody_partner_id
if not partner:
raise UserError(_("Please set a Custody Partner before proceeding."))
if not partner.property_account_payable_id:
raise UserError(_("The Custody Partner %s has no payable account configured.") % partner.name)
account_dest = partner.property_account_payable_id.id
# elif self.payment_mode == 'custody':
# if self.sheet_id.account_payment_method_id:
# account_dest = self.sheet_id.account_payment_method_id.payment_account_id.id
# else:
# if not self.sheet_id.custody_bank_journal_id.payment_credit_account_id:
# raise UserError(
# _("No Outstanding Payments Account found for the %s journal, please configure one.") % (
# self.sheet_id.custody_bank_journal_id.name))
# account_dest = self.sheet_id.custody_bank_journal_id.payment_credit_account_id.id
else:
return super(HrExpense, self)._get_expense_account_destination()
@ -74,13 +115,13 @@ class HrExpense(models.Model):
def _prepare_move_values(self):
"""Override to handle custody payment mode like company_account"""
self.ensure_one()
if self.payment_mode == 'company_account':
journal = self.sheet_id.bank_journal_id
elif self.payment_mode == 'custody':
journal = self.sheet_id.custody_bank_journal_id or self.sheet_id.bank_journal_id
else:
journal = self.sheet_id.journal_id
journal = self.sheet_id.custody_bank_journal_id or self.sheet_id.bank_journal_id
# if self.payment_mode == 'company_account':
# journal = self.sheet_id.bank_journal_id
# elif self.payment_mode == 'custody':
# journal = self.sheet_id.custody_bank_journal_id or self.sheet_id.bank_journal_id
# else:
# journal = self.sheet_id.journal_id
account_date = self.sheet_id.accounting_date or self.date
move_values = {
@ -89,38 +130,165 @@ class HrExpense(models.Model):
'date': account_date,
'ref': self.sheet_id.name,
'name': '/',
'move_type' :'in_receipt',
'invoice_date': self.date,
'partner_id': self.sheet_id.custody_partner_id.id
}
if self.payment_mode == 'custody':
move_values['is_petty_paid'] = True
move_values['petty_employee_id'] = self.sheet_id.employee_id.id
return move_values
def action_move_create(self):
move_group_by_sheet = super().action_move_create()
def send_attachments_to_move(self):
expense_attachments = self.env['ir.attachment'].search([
('res_model', '=', 'hr.expense'),
('res_id', 'in', self.ids)
])
sheet_attachments = self.env['ir.attachment'].search([
('res_model', '=', 'hr.expense.sheet'),
('res_id', 'in', self.mapped('sheet_id').ids)
])
for expense in self:
if expense.payment_mode == 'custody':
move = expense.sheet_id.account_move_id
partner_id = expense.sheet_id.custody_partner_id.id if expense.sheet_id.custody_partner_id else \
expense.employee_id.sudo().address_home_id.commercial_partner_id.id
amount =0
for line in move.line_ids:
if line.credit > 0:
amount += line.credit
line.write({'partner_id': partner_id})
if move.state == 'posted':
print("amount",amount)
employee = self.env['hr.employee'].sudo().search([
('user_id.partner_id', '=', partner_id)
], limit=1)
move = expense.sheet_id.account_move_id
for att in expense_attachments + sheet_attachments:
att.copy({'res_model': 'account.move', 'res_id': move.id})
if employee:
def action_move_create(self):
expenses = self.filtered(lambda x: not x.is_refused)
if not expenses:
return {}
self.env['hr.request.pledge'].allocate_payment_to_pledges(
employee_id=employee.id,
journal_id=move.journal_id.id,
amount=amount
)
move_group_by_sheet = expenses._get_account_move_by_sheet()
move_line_values_by_expense = expenses._get_account_move_line_values()
print(f"✅ posted {move.name} expense {expense.name}")
for expense in expenses:
move = move_group_by_sheet[expense.sheet_id.id]
move_line_values = move_line_values_by_expense.get(expense.id)
move.write({'line_ids': [(0, 0, line) for line in move_line_values]})
expense.sheet_id.write({'account_move_id': move.id})
if expense.payment_mode == 'company_account':
expense.sheet_id.paid_expense_sheets()
for expense in expenses:
move = move_group_by_sheet[expense.sheet_id.id]
# if expense.payment_mode == 'custody':
# if move.state != 'posted':
# move.action_budget_management()
partner_id = (
expense.sheet_id.custody_partner_id.id
if expense.sheet_id.custody_partner_id
else expense.employee_id.sudo().address_home_id.commercial_partner_id.id
)
amount = 0
for line in move.line_ids:
if line.debit > 0:
amount += line.debit
line.write({'partner_id': partner_id})
# else:
# if move.state != 'posted':
# move._post()
self.send_attachments_to_move()
return move_group_by_sheet
def _get_account_move_line_values(self):
move_line_values_by_expense = {}
for expense in self:
move_line_name = expense.employee_id.name + ': ' + expense.name.split('\n')[0][:64]
account_src = expense._get_expense_account_source()
account_dst = expense._get_expense_account_destination()
account_date = expense.date or expense.sheet_id.accounting_date or fields.Date.context_today(expense)
company_currency = expense.company_id.currency_id
move_line_values = []
taxes = expense.tax_ids.with_context(round=True).compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id)
total_amount = 0.0
total_amount_currency = 0.0
partner_id = expense.employee_id.sudo().address_home_id.commercial_partner_id.id
# source move line
balance = expense.currency_id._convert(taxes['total_excluded'], company_currency, expense.company_id, account_date)
amount_currency = taxes['total_excluded']
move_line_src = {
'name': move_line_name,
'quantity': expense.quantity or 1,
'debit': balance if balance > 0 else 0,
'credit': -balance if balance < 0 else 0,
'amount_currency': amount_currency,
'account_id': account_src.id,
'product_id': expense.product_id.id,
'product_uom_id': expense.product_uom_id.id,
'analytic_account_id': expense.analytic_account_id.id,
'analytic_tag_ids': [(6, 0, expense.analytic_tag_ids.ids)],
'expense_id': expense.id,
'partner_id': partner_id,
'tax_ids': [(6, 0, expense.tax_ids.ids)],
'tax_tag_ids': [(6, 0, taxes['base_tags'])],
'currency_id': expense.currency_id.id,
}
move_line_values.append(move_line_src)
total_amount -= balance
total_amount_currency -= move_line_src['amount_currency']
# taxes move lines
for tax in taxes['taxes']:
balance = expense.currency_id._convert(tax['amount'], company_currency, expense.company_id, account_date)
amount_currency = tax['amount']
if tax['tax_repartition_line_id']:
rep_ln = self.env['account.tax.repartition.line'].browse(tax['tax_repartition_line_id'])
base_amount = self.env['account.move']._get_base_amount_to_display(tax['base'], rep_ln)
base_amount = expense.currency_id._convert(base_amount, company_currency, expense.company_id, account_date)
else:
print(f"❌ not posted {expense.name}")
base_amount = None
move_line_tax_values = {
'name': tax['name'],
'quantity': 1,
'debit': balance if balance > 0 else 0,
'credit': -balance if balance < 0 else 0,
'amount_currency': amount_currency,
'account_id': tax['account_id'] or move_line_src['account_id'],
'tax_repartition_line_id': tax['tax_repartition_line_id'],
'tax_tag_ids': tax['tag_ids'],
'tax_base_amount': base_amount,
'expense_id': expense.id,
'partner_id': partner_id,
'currency_id': expense.currency_id.id,
'analytic_account_id': expense.analytic_account_id.id if tax['analytic'] else False,
'analytic_tag_ids': [(6, 0, expense.analytic_tag_ids.ids)] if tax['analytic'] else False,
}
total_amount -= balance
total_amount_currency -= move_line_tax_values['amount_currency']
move_line_values.append(move_line_tax_values)
# destination move line
move_line_dst = {
'name': move_line_name,
'debit': total_amount > 0 and total_amount,
'credit': total_amount < 0 and -total_amount,
'account_id': account_dst,
'date_maturity': account_date,
'amount_currency': total_amount_currency,
'currency_id': expense.currency_id.id,
'expense_id': expense.id,
'partner_id': partner_id,
'exclude_from_invoice_tab': True,
}
move_line_values.append(move_line_dst)
move_line_values_by_expense[expense.id] = move_line_values
return move_line_values_by_expense
return move_group_by_sheet

View File

@ -18,23 +18,23 @@ class HrExpenseSheetPledge(models.Model):
custody_bank_journal_id = fields.Many2one(
'account.journal',
string='Custody Journal',
domain=[('custody_journal', '=', True)],
)
custody_partner_id = fields.Many2one('res.partner', string='Custody Partner')
string='Journal',
domain="[('custody_journal', '=', True), ('type', 'in', ['purchase'])]",
@api.depends('custody_bank_journal_id', 'bank_journal_id')
def _compute_available_account_payment_method_ids(self):
AccountPaymentMethodLine = self.env['account.payment.method.line'].sudo()
for rec in self:
if rec.payment_mode == 'custody' and rec.custody_bank_journal_id:
rec.available_account_payment_method_ids = AccountPaymentMethodLine.search(
[('id', 'in', rec.custody_bank_journal_id.outbound_payment_method_line_ids.ids)])
elif rec.bank_journal_id:
rec.available_account_payment_method_ids = AccountPaymentMethodLine.search(
[('id', 'in', rec.bank_journal_id.outbound_payment_method_line_ids.ids)])
else:
rec.available_account_payment_method_ids = False
)
custody_partner_id = fields.Many2one('res.partner', string='Partner')
@api.onchange('employee_id')
def _onchange_employee_id_set_custody_partner(self):
if self.employee_id and self.employee_id.address_home_id:
self.custody_partner_id = self.employee_id.address_home_id.id
@api.model
def create(self, vals):
if vals.get('employee_id') and not vals.get('custody_partner_id'):
employee = self.env['hr.employee'].browse(vals['employee_id'])
if employee.address_home_id:
vals['custody_partner_id'] = employee.address_home_id.id
return super().create(vals)
def action_submit_sheet(self):
for rec in self:
@ -43,33 +43,22 @@ class HrExpenseSheetPledge(models.Model):
_("When the payment type is custody, you must set the Custody Partner before submitting."))
return super(HrExpenseSheetPledge, self).action_submit_sheet()
def _get_custody_partner_domain(self):
if not self.custody_bank_journal_id:
return [('id', '=', False)]
pledge_requests = self.env['hr.request.pledge'].search([
('journal_id', '=', self.custody_bank_journal_id.id),
('remaining_amount', '>', 0),
])
employee_ids = pledge_requests.mapped('employee_id')
partner_ids = []
for employee in employee_ids:
if employee.user_id and employee.user_id.partner_id:
partner_ids.append(employee.user_id.partner_id.id)
if not partner_ids:
return [('id', '=', False)]
return [('id', 'in', partner_ids)]
@api.onchange('payment_mode')
def _onchange_payment_mode(self):
"""Reset fields when payment mode changes"""
if self.payment_mode == 'custody':
self.bank_journal_id = False
self.account_payment_method_id = False
self.custody_partner_id = False
else:
self.custody_bank_journal_id = False
self.custody_partner_id = False
self.custody_bank_journal_id = False
self.bank_journal_id = False
self.account_payment_method_id = False
# if self.payment_mode == 'custody':
# self.bank_journal_id = False
# self.account_payment_method_id = False
# # self.custody_partner_id = False
# else:
# self.custody_bank_journal_id = False
# self.custody_partner_id = False
@api.onchange('custody_bank_journal_id')
def _onchange_custody_bank_journal_id(self):
@ -78,22 +67,36 @@ class HrExpenseSheetPledge(models.Model):
self.bank_journal_id = self.custody_bank_journal_id
self.account_payment_method_id = False
self.custody_partner_id = False
return {'domain': {'custody_partner_id': self._get_custody_partner_domain()}}
@api.onchange('bank_journal_id')
def _onchange_bank_journal_id(self):
self.account_payment_method_id = False
if self.payment_mode == 'custody':
self.custody_partner_id = False
return {'domain': {'custody_partner_id': self._get_custody_partner_domain()}}
# self.custody_partner_id = False
# return {'domain': {'custody_partner_id': self._get_custody_partner_domain()}}
def action_sheet_move_create(self):
"""Override to handle custody like company_account"""
res = super().action_sheet_move_create()
custody_sheets = self.filtered(lambda sheet: sheet.payment_mode == 'custody' and sheet.expense_line_ids)
custody_sheets = self.filtered(lambda sheet: sheet.expense_line_ids)
if custody_sheets:
for sheet in custody_sheets:
print("🧾 Sheet ID %s current state: %s", sheet.id, sheet.state)
if custody_sheets.account_move_id:
custody_sheets.account_move_id.write({'move_type': 'in_receipt'})
custody_sheets.paid_expense_sheets()
return res
def approve_expense_sheets(self):
for sheet in self:
if sheet.payment_mode == 'custody':
employee = sheet.employee_id
available_pledges = self.env['hr.request.pledge'].sudo().search([
('employee_id', '=', employee.id),
('custody_status', 'in', ['partial', 'new']),
])
available_pledges = available_pledges.filtered(
lambda p: p.custody_status in ['partial', 'new'] and p.state == 'pay'
)
if not available_pledges:
raise UserError(
_("The employee %s has no available custody balance to cover this expense sheet.") % employee.name
)
return super().approve_expense_sheets()

View File

@ -5,6 +5,10 @@ from odoo import api, fields, models, tools, _
import ast
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_is_zero
from datetime import datetime, timedelta
# class HrEmployee(models.Model):
@ -20,15 +24,7 @@ from odoo.tools import float_compare, float_is_zero
# return action
class BaseDashboardExtended(models.Model):
_inherit = 'base.dashbord' # Inherit existing dashboard
# One2many reverse of the Many2one in pledges
pledge_ids = fields.One2many(
'hr.request.pledge',
'dashboard_id',
)
class HrRequestPledge(models.Model):
_name = 'hr.request.pledge'
@ -42,10 +38,10 @@ class HrRequestPledge(models.Model):
('submit', _('Waiting Payroll Officer')),
('direct_manager', _('Wait HR Department')),
('hr_manager', _('Wait GM Approval')),
('executive_manager', _('Wait Transfer')),
('financial_approve', _('Wait Financial Approval')),
('pay', _('Transferred')), ('refused', _('Refused')),
('locked', _('Locked')),
('closed', _('Loan Suspended'))],
default="draft", tracking=True)
date = fields.Date(required=True)
@ -54,13 +50,13 @@ class HrRequestPledge(models.Model):
job_id = fields.Many2one(related='employee_id.job_id', readonly=True)
dashboard_id = fields.Many2one(
'base.dashbord',
index=True
)
is_financial_impact = fields.Boolean(
compute='_compute_is_financial_impact',
store=True
)
# is_financial_impact = fields.Boolean(
# compute='_compute_is_financial_impact',
# store=True
# )
custody_type_id = fields.Many2one(
'custody.types',
string='Custody Types',
@ -82,7 +78,13 @@ class HrRequestPledge(models.Model):
string='Currency',
default=lambda self: self.env.company.currency_id
)
company_id = fields.Many2one(
"res.company",
string="Company",
default=lambda self: self.env.company,
required=True,
readonly=True
)
spent_amount = fields.Float(string="Amount Spent", default=0.0)
remaining_amount = fields.Float(string="Amount Remaining", compute="_compute_remaining_amount", store=True)
custody_status = fields.Selection([
@ -90,32 +92,169 @@ class HrRequestPledge(models.Model):
('partial', 'Partial'),
('paid', 'Paid'),
('exceeded', 'Exceeded')
], string='Custody Status', default='new', tracking=True)
],compute='_compute_custody_status', string='Custody Status', default='new', tracking=True)
@api.depends('spent_amount', 'emp_expect_amount')
payment_ids = fields.One2many(
'account.payment', 'hr_request_pledge_id',
string="Payments"
)
total_paid_amount = fields.Float(
string="Total Paid Amount",
compute='_compute_total_paid_amount',
store=True
)
permanent_pledge = fields.Boolean(
string="Permanent Pledge",
default=False,
)
spent_amount_computed = fields.Float(
string="Computed Spent Amount",
compute="_compute_spent_amount_from_moves",
store=True,
help="Automatically distributed from related expense journal entries by employee."
)
total_refunded_amount = fields.Float(
string="Total Refunded Amount",
compute='_compute_total_refunded_amount',
store=True
)
@api.depends('payment_ids.amount', 'payment_ids.state', 'payment_ids.payment_type')
def _compute_total_refunded_amount(self):
for rec in self:
# Filter posted inbound payments
posted_payments = rec.payment_ids.filtered(lambda p: p.state == 'posted')
inbound_total = sum(posted_payments.filtered(lambda p: p.payment_type == 'inbound').mapped('amount'))
rec.total_refunded_amount = inbound_total
def action_lock_pledge(self):
for record in self:
if record.remaining_amount > 0:
raise UserError(_("You cannot close this pledge because there is a remaining amount of %.2f.") % record.remaining_amount)
record.state = 'locked'
@api.depends('employee_id', 'total_paid_amount')
def _compute_spent_amount_from_moves(self):
Move = self.env['account.move']
all_employees = self.mapped('employee_id')
for emp in all_employees:
moves = Move.search([
('move_type', 'in', ['in_receipt', 'in_invoice']),
('is_petty_paid', '=', True),
('state', '=', 'posted'),
('petty_employee_id', '=', emp.id),
])
total_spent = 0.0
for move in moves:
total_spent += sum(move.line_ids.filtered(lambda l: l.debit > 0).mapped('debit'))
pledges = self.search([('employee_id', '=', emp.id), ('state', 'in', ['pay', 'locked'])], order='id asc')
remaining = total_spent
for pledge in pledges:
if remaining <= 0:
pledge.spent_amount_computed = 0.0
pledge.spent_amount = 0.0
continue
limit = pledge.total_paid_amount or 0.0
if remaining >= limit and limit > 0:
pledge.spent_amount_computed = limit
pledge.spent_amount = limit
remaining -= limit
else:
pledge.spent_amount_computed = remaining
pledge.spent_amount = remaining
remaining = 0
if remaining > 0 and pledges:
pledges[-1].spent_amount_computed += remaining
pledges[-1].spent_amount += remaining
@api.depends('spent_amount', 'total_paid_amount')
def _compute_custody_status(self):
for pledge in self:
remaining = (pledge.total_paid_amount or 0.0) - (pledge.spent_amount or 0.0)
if remaining > 0:
pledge.custody_status = 'partial'
elif remaining == 0:
pledge.custody_status = 'paid'
else:
pledge.custody_status = 'exceeded'
# @api.depends('payment_ids.amount', 'payment_ids.state', 'payment_ids.payment_type')
# def _compute_total_paid_amount(self):
# for rec in self:
# posted_outbound_payments = rec.payment_ids.filtered(
# lambda p: p.state == 'posted' and p.payment_type == 'outbound')
# rec.total_paid_amount = sum(posted_outbound_payments.mapped('amount'))
@api.depends('payment_ids.amount', 'payment_ids.state', 'payment_ids.payment_type')
def _compute_total_paid_amount(self):
for rec in self:
posted_payments = rec.payment_ids.filtered(lambda p: p.state == 'posted')
outbound_total = sum(posted_payments.filtered(lambda p: p.payment_type == 'outbound').mapped('amount'))
rec.total_paid_amount = outbound_total
@api.depends('spent_amount', 'total_paid_amount')
def _compute_remaining_amount(self):
for rec in self:
rec.remaining_amount = (rec.emp_expect_amount or 0.0) - (rec.spent_amount or 0.0)
rec.remaining_amount = (rec.total_paid_amount or 0.0) - (rec.spent_amount or 0.0) - (rec.total_refunded_amount or 0.0)
@api.model
def search(self, args, offset=0, limit=None, order=None, count=False):
if not self.env.su:
user_company_ids = self.env.user.company_ids.ids
company_domain = [('company_id', 'in', user_company_ids)]
args = args + company_domain
return super(HrRequestPledge, self).search(args, offset=offset, limit=limit, order=order, count=count)
@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
if not self.env.context.get('skip_company_check'):
company_domain = [('company_id', 'in', self.env.user.company_ids.ids)]
domain = (domain or []) + company_domain
return super(HrRequestPledge, self).search_read(
domain, fields, offset, limit, order
)
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
if not self.env.su:
user_company_ids = self.env.user.company_ids.ids
company_domain = [('company_id', 'in', user_company_ids)]
domain = domain + company_domain
return super(HrRequestPledge, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
# def unlink(self):
# for i in self:
# if i.state != 'draft':
# raise UserError(_('You can not delete record in state not in draft'))
# return super(HrRequestPledge, self).unlink()
@api.model
def allocate_payment_to_pledges(self, employee_id, journal_id, amount):
def allocate_payment_to_pledges(self, employee_id, amount):
"""
Allocate a payment amount to employee's custody requests ordered by oldest.
Handles partial, full, and exceeded states.
"""
print(f"🚀 allocate_payment_to_pledges called | employee_id={employee_id} | amount={amount}")
remaining_amount = amount
pledges = self.search([
('employee_id', '=', employee_id),
('journal_id', '=', journal_id),
('custody_status', 'in', ['partial','new' ]),
], order="id asc")
print(f"🔍 pledges found: {pledges}")
for pledge in pledges:
if remaining_amount <= 0:
@ -160,22 +299,25 @@ class HrRequestPledge(models.Model):
"The requested amount (%s) exceeds the maximum allowed for the selected custody type (%s)."
) % (record.emp_expect_amount, record.custody_type_id.max_custody_amount))
@api.depends('dashboard_id.is_financial_impact')
def _compute_is_financial_impact(self):
for record in self:
record.is_financial_impact = record.dashboard_id.is_financial_impact
# @api.depends('dashboard_id.pledge_ids.is_financial_impact')
# def _compute_is_financial_impact(self):
# for record in self:
# if record.dashboard_id:
# record.is_financial_impact = any(
# pledge.is_financial_impact for pledge in record.dashboard_id.pledge_ids
# )
# else:
# record.is_financial_impact = False
# record.is_financial_impact = record.dashboard_id.pledge_ids.is_financial_impact
def get_user_id(self):
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
if employee_id:
return employee_id.id
else:
return False
# @api.model
@ -183,8 +325,8 @@ class HrRequestPledge(models.Model):
# res = super().default_get(fields)
# res['journal_id'] = False
# return res
@api.constrains('emp_expect_amount')
def _check_positive_emp_expect_amount(self):
for rec in self:
@ -195,7 +337,7 @@ class HrRequestPledge(models.Model):
@api.model
def create(self, values):
# Auto-link dashboard if not provided
if not values.get('dashboard_id'):
dashboard = self.env['base.dashbord'].search([ # Fix typo: 'base.dashboard'
@ -203,11 +345,11 @@ class HrRequestPledge(models.Model):
], limit=1)
if dashboard:
values['dashboard_id'] = dashboard.id
# Generate sequence code (your existing logic)
seq = self.env['ir.sequence'].next_by_code('hr.request.pledge') or '/'
values['code'] = seq # Assign the sequence to the 'code' field
# Create the record
return super(HrRequestPledge, self).create(values)
@ -229,77 +371,213 @@ class HrRequestPledge(models.Model):
def cancel(self):
self.state = "cancel"
def action_open_confirm_wizard(self):
self.ensure_one()
return {
'name': _('Confirmation Wizard for Pledge Payment'),
'type': 'ir.actions.act_window',
'res_model': 'hr.request.pledge.confirm.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_pledge_id': self.id,
'default_action_type': self._context.get('action_type'),
},
}
def pay(self):
if not self.journal_id:
raise ValidationError(_('Please set the journal for this employee.'))
employee_partner = self.employee_id.user_id.partner_id
if not employee_partner:
raise ValidationError(_('Employee must have a related partner.'))
action_type = self.env.context.get('action_type', 'create')
if action_type == 'feed':
amount = self.spent_amount or 0.0
if amount <= 0:
raise ValidationError(_('Spent amount must be greater than zero for feeding the pledge.'))
else:
amount = self.emp_expect_amount or 0.0
payment_vals = {
'payment_type': 'inbound',
'payment_type': 'outbound',
'partner_type': 'supplier',
'is_internal_transfer': True,
'amount': self.emp_expect_amount,
'skip_paired_payment': True,
'amount': amount,
'journal_id': self.journal_id.id,
'date': fields.datetime.today(),
'ref': self.description,
'hr_request_pledge': self.id,
'hr_request_pledge_id': self.id,
'partner_id': employee_partner.id,
'petty_cash_pledge':True
}
payment = self.env['account.payment'].create(payment_vals)
payment.flush()
payment.refresh()
employee_partner = self.employee_id.user_id.partner_id
if employee_partner:
for line in payment.move_id.line_ids:
if line.debit > 0:
line.partner_id = employee_partner.id
break
# employee_partner = self.employee_id.user_id.partner_id
# if employee_partner and payment.move_id:
# debit_lines = payment.move_id.line_ids.filtered(lambda l: l.debit > 0)
# if debit_lines:
# debit_lines.write({'partner_id': employee_partner.id})
self.state = "pay"
return payment
if action_type == 'create':
self.state = "pay"
if action_type == 'feed':
message = _("✅ The pledge has been successfully funded with an amount of %.2f.") % amount
else:
message = _("✅ A new pledge has been successfully created with an amount of %.2f.") % amount
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Operation Successful'),
'message': message,
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
}
}
def refund_remaining_amount(self):
for rec in self:
if rec.remaining_amount <= 0:
raise ValidationError(_('There is no remaining amount to refund.'))
if not rec.journal_id:
raise ValidationError(_('Please set the journal for this employee.'))
employee_partner = rec.employee_id.user_id.partner_id
if not employee_partner:
raise ValidationError(_('Employee must have a related partner.'))
amount = rec.remaining_amount
payment_vals = {
'payment_type': 'inbound',
'partner_type': 'customer',
'is_internal_transfer': True,
'skip_paired_payment': True,
'amount': amount,
'journal_id': rec.journal_id.id,
'date': fields.datetime.today(),
'ref': _('Refund of pledge: %s') % rec.code,
'hr_request_pledge_id': rec.id,
'partner_id': employee_partner.id,
'petty_cash_pledge': True,
}
payment = self.env['account.payment'].create(payment_vals)
payment.flush()
payment.refresh()
message = _("تم إرجاع المبلغ")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Operation Successful'),
'message': message,
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
}
}
def action_open_refund_payments(self):
refund_payments = self.env['account.payment'].search([
'|',
('hr_request_pledge_id', '=', self.id),
('hr_request_pledge', '=', self.id),
('payment_type', '=', 'inbound'),
])
return {
'type': 'ir.actions.act_window',
'name': 'Refund Payments',
'res_model': 'account.payment',
'domain': [('id', 'in', refund_payments.ids)] if refund_payments else [('id', '=', False)],
'view_mode': 'tree,form',
'target': 'current',
}
def financialApproval(self):
self.state="financial_approve"
self.state = "financial_approve"
def action_open_payment_confirmation(self):
"""
"""
return {
'name': _('Confirm Payment'),
'type': 'ir.actions.act_window',
'res_model': 'hr.request.pledge.pay.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_pledge_id': self.id, 'action_type': self.env.context.get('action_type')}
}
def action_account_payment_budget_pledge(self):
budget_account_payment = self.env['account.payment'].search(
[('hr_request_pledge', '=', self.id)],
limit=1
)
treeview_ref = self.env.ref('account.view_account_payment_tree', False) # tree view
formview_ref = self.env.ref('odex25_account_saip.view_account_payment_new_approve_form', False)
budget_account_payment = self.env['account.payment'].search([
'|',
('hr_request_pledge_id', '=', self.id),
('hr_request_pledge', '=', self.id),
('payment_type', '=', 'outbound'),
])
if budget_account_payment:
return {
'type': 'ir.actions.act_window',
'name': 'Achieving Budget',
'res_model': 'account.payment',
'res_id': budget_account_payment.id,
'view_mode': 'form',
'domain': [('id', 'in', budget_account_payment.ids)] if budget_account_payment else [],
'view_mode': 'tree,form',
'views': [
(treeview_ref.id, 'tree') if treeview_ref else (False, 'tree'),
(formview_ref.id, 'form') if formview_ref else (False, 'form'),
],
'target': 'current',
}
class SmartButtonReturnRequestPledge(models.Model):
_inherit = "account.payment"
hr_request_pledge = fields.Many2one('hr.request.pledge')
def action_go_to_hr_request_pledge(self):
pledge_record = self.env['hr.request.pledge'].search(
[('id', '=', self.hr_request_pledge.id)],
limit=1
class AccountMove(models.Model):
_inherit = 'account.move'
def action_post(self):
res = super(AccountMove, self).action_post()
petty_moves = self.filtered(lambda m:
m.move_type in ['in_receipt', 'in_invoice'] and
m.is_petty_paid and
m.state == 'posted'
)
if pledge_record:
return {
'type': 'ir.actions.act_window',
'name': 'HR Request Pledge',
'res_model': 'hr.request.pledge',
'res_id': pledge_record.id,
'view_mode': 'form',
'view_type': 'form',
'target': 'current',
}
if petty_moves:
employees = petty_moves.mapped('petty_employee_id')
if employees:
pledges = self.env['hr.request.pledge'].search([
('employee_id', 'in', employees.ids),
('state', 'in', ['pay', 'locked'])
])
if pledges:
pledges._compute_spent_amount_from_moves()
return res
class BaseDashboardExtended(models.Model):
_inherit = 'base.dashbord' # Inherit existing dashboard
pledge_ids = fields.One2many(
'hr.request.pledge',
'dashboard_id',
)
class EmployeeJournal(models.Model):
@ -318,59 +596,6 @@ class EmployeeJournal(models.Model):
# def action_sheet_move_create(self):
# for sheet in self:
# if sheet.payment_mode == 'company_account' and not (sheet.bank_journal_id and sheet.account_payment_method_id):
# raise UserError(
# _("Please enter Bank Journal and Payment Method!")
# )
#
# return super(HrExpenseSheetPledge, self).action_sheet_move_create()
# class AchievingBudgetAchievingBudget(models.Model):
# _inherit = "hr.expense"
# action_budget_id = fields.Many2one('account.tax', 'analytic')
# def _create_sheet_from_expenses(self):
# if any(expense.state != 'draft' or expense.sheet_id for expense in self):
# raise UserError(_("You cannot report twice the same line!"))
# if len(self.mapped('employee_id')) != 1:
# raise UserError(_("You cannot report expenses for different employees in the same report."))
# if any(not expense.product_id for expense in self):
# raise UserError(_("You can not create report without product."))
# if len(self.company_id) != 1:
# raise UserError(_("You cannot report expenses for different companies in the same report."))
# todo = self.filtered(lambda x: x.payment_mode=='own_account') or self.filtered(lambda x: x.payment_mode=='company_account')
# sheet = self.env['hr.expense.sheet'].create({
# 'company_id': self.company_id.id,
# 'employee_id': self[0].employee_id.id,
# 'name': todo[0].name if len(todo) == 1 else '',
# 'expense_line_ids': [(6, 0, todo.ids)],
# })
# # sheet.write({
# # 'account_payment_method_id': sheet.bank_journal_id.outbound_payment_method_line_ids[:1]
# # })
# return sheet
# def action_achieving_budget_pledge(self):
# achieving_budget_record = self.env['budget.confirmation'].search(
# [('expense_id', '=', self.id)],
# limit=1
# )
# if achieving_budget_record:
# return {
# 'type': 'ir.actions.act_window',
# 'name': 'Achieving Budget',
# 'res_model': 'budget.confirmation',
# 'res_id': achieving_budget_record.id,
# 'view_mode': 'form',
# 'target': 'current',
# }
class MoveLine(models.Model):
_inherit = 'account.move.line'

View File

@ -16,7 +16,7 @@ class CustodyTypes(models.Model):
'account.journal',
string='Journal',
required=True,
domain="[('custody_journal', '=', True)]",
domain="[('custody_journal', '=', True), ('type', 'in', ['bank','cash'])]",
help='Select the journal associated with this type of custody'
)
@ -27,6 +27,48 @@ class CustodyTypes(models.Model):
help='The maximum amount allowed for this type of custody'
)
company_id = fields.Many2one(
"res.company",
string="Company",
default=lambda self: self.env.company,
required=True,
readonly=True
)
@api.model
def search(self, args, offset=0, limit=None, order=None, count=False):
if not self.env.su:
user_company_ids = self.env.user.company_ids.ids
company_domain = [('company_id', 'in', user_company_ids)]
args = args + company_domain
return super(CustodyTypes, self).search(args, offset=offset, limit=limit, order=order, count=count)
@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
if not self.env.context.get('skip_company_check'):
company_domain = [('company_id', 'in', self.env.user.company_ids.ids)]
domain = (domain or []) + company_domain
return super(CustodyTypes, self).search_read(
domain, fields, offset, limit, order
)
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
if not self.env.su:
user_company_ids = self.env.user.company_ids.ids
company_domain = [('company_id', 'in', user_company_ids)]
domain = domain + company_domain
return super(CustodyTypes, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
args = args or []
if not self.env.su:
user_company_ids = self.env.user.company_ids.ids
args = args + [('company_id', 'in', user_company_ids)]
return super(CustodyTypes, self).name_search(name=name, args=args, operator=operator, limit=limit)
@api.constrains('max_custody_amount')
def _check_max_custody_amount(self):
for record in self:

View File

@ -1,4 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_request_pledge_user,access_hr_request_pledge_user,model_hr_request_pledge,,1,1,1,1
access_hr_request_pledge_confirm_wizard,access_hr_request_pledge_confirm_wizard,model_hr_request_pledge_confirm_wizard,,1,1,1,1
access_custody_types_manager,custody.types.manager,model_custody_types,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_request_pledge_user access_hr_request_pledge_user model_hr_request_pledge 1 1 1 1
3 access_custody_types_manager access_hr_request_pledge_confirm_wizard custody.types.manager access_hr_request_pledge_confirm_wizard model_custody_types model_hr_request_pledge_confirm_wizard 1 1 1 1
4 access_custody_types_manager custody.types.manager model_custody_types 1 1 1 1

View File

@ -31,23 +31,44 @@
<field name="name">Officer</field>
<field name="category_id" ref="employee_custody_request.module_category_general_ledger_custom"/>
</record>
<record id="hr_request_pledge_rule_hr_user" model="ir.rule">
<field name="name">HR User: Own Pledges</field>
<field name="model_id" ref="employee_custody_request.model_hr_request_pledge"/>
<field name="groups" eval="[(4, ref('employee_custody_request.group_hr_user'))]"/>
<field name="domain_force">[('employee_id.user_id', '=', user.id)]</field>
</record>
<record id="group_loan_manager" model="res.groups">
<field name="name">Loan Manager</field>
<field name="category_id" ref="employee_custody_request.module_category_general_ledger_custom"/>
</record>
<record id="group_hr_manager" model="res.groups">
<field name="name">Administrator</field>
<field name="category_id" ref="employee_custody_request.module_category_general_ledger_custom"/>
</record>
<record id="group_general_manager" model="res.groups">
<field name="name">General Manager</field>
<field name="category_id" ref="employee_custody_request.module_category_general_ledger_custom"/>
</record>
<record id="group_account_manager" model="res.groups">
<field name="name">Accounting Manager</field>
<field name="category_id" ref="employee_custody_request.module_category_general_ledger_custom"/>
</record>
<record id="hr_request_pledge_rule_loan_manager" model="ir.rule">
<field name="name"> All Pledges</field>
<field name="model_id" ref="employee_custody_request.model_hr_request_pledge"/>
<field name="groups" eval="[(4, ref('employee_custody_request.group_loan_manager')),
(4, ref('employee_custody_request.group_general_manager')),
(4, ref('employee_custody_request.group_hr_manager')),
(4, ref('employee_custody_request.group_account_manager')),
]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
<record id="group_employee_custody_report" model="res.groups">
<field name="name">Employee Custody Report</field>
<field name="category_id" ref="employee_custody_request.module_category_general_ledger_custom"/>

View File

@ -4,11 +4,18 @@
<field name="model">hr.expense.sheet</field>
<field name="inherit_id" ref="hr_expense.view_hr_expense_sheet_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='bank_journal_id']" position="after">
<field name="custody_bank_journal_id" attrs="{'invisible': [('payment_mode', '!=', 'custody')]}"/>
<xpath expr="//field[@name='bank_journal_id']" position="replace">
<field name="custody_bank_journal_id" required="1" />
</xpath>
<xpath expr="//field[@name='custody_bank_journal_id']" position="after">
<field name="custody_partner_id" attrs="{'invisible': [('payment_mode', '!=', 'custody')]}"/>
<field name="custody_partner_id" readonly="True"/>
</xpath>
<xpath expr="//field[@name='journal_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_register_payment']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>

View File

@ -30,6 +30,9 @@
<field name="journal_id"/>
<field name="max_custody_amount"/>
</group>
<group>
<field name="company_id"/>
</group>
</group>
</sheet>

View File

@ -50,8 +50,17 @@
class="oe_stat_button" icon="fa-bars"
attrs="{'invisible': [('hr_request_pledge', '=', False)]}"/>
</xpath> -->
<xpath expr="//field[@name='is_internal_transfer']" position="attributes">
<attribute name="attrs">{'readonly': [('petty_cash_pledge', '=', True)]}</attribute>
</xpath>
<xpath expr="//field[@name='payment_type']" position="after">
<field name="hr_request_pledge" invisible="1"/>
<field name="hr_request_pledge_id" invisible="1"/>
</xpath>
<xpath expr="//field[@name='move_id']" position="after">
<field name="petty_cash_pledge" readonly="1" attrs="{'invisible': [('petty_cash_pledge','=',False)]}"/>
<field name="original_move_id" readonly="1" attrs="{'invisible': [('original_move_id','=',False)]}"/>
</xpath>
</field>
@ -62,45 +71,59 @@
<field name="arch" type="xml">
<form>
<header>
<button name="action_lock_pledge"
string="Close Pledge"
type="object"
class="btn-primary"
states="pay"/>
<button name="submit" string="Submit" class="oe_highlight" type="object"
states="draft" groups="employee_custody_request.group_hr_user"/>
<button name="action_open_confirm_wizard" string="Feed Pledge"
type="object"
class="oe_highlight"
context="{'action_type': 'feed'}"
groups="employee_custody_request.group_hr_user"
attrs="{'invisible': ['|', ('state','!=','pay'), ('permanent_pledge','!=',True)]}"/>
<button name="refund_remaining_amount" string="Refund"
type="object"
class="oe_highlight"
groups="employee_custody_request.group_hr_user"
attrs="{'invisible': ['|', ('state','!=','pay'), ('permanent_pledge','!=',True)]}"/>
<button name="direct_manager" string="Payroll Officer Approve" class="oe_highlight"
type="object" states="submit" groups="employee_custody_request.group_loan_manager"/>
<button name="refused" string="Refuse" states="submit" type="object"
class="oe_highlight" groups="employee_custody_request.group_loan_manager"/>
<button name="hr_manager" string="HR Manager Approve" class="oe_highlight" type="object"
states="direct_manager" groups="employee_custody_request.group_hr_manager"/>
<button name="refused" string="Refuse" states="direct_manager" type="object"
class="oe_highlight" groups="employee_custody_request.group_hr_manager"/>
<!-- <button name="hr_manager" string="HR Manager Approve" class="oe_highlight" type="object"-->
<!-- states="direct_manager" groups="employee_custody_request.group_hr_manager"/>-->
<!-- <button name="refused" string="Refuse" states="direct_manager" type="object"-->
<!-- class="oe_highlight" groups="employee_custody_request.group_hr_manager"/>-->
<button name="executive_manager" string="GM Manager Approve" class="oe_highlight" type="object"
states="hr_manager" groups="employee_custody_request.group_general_manager"/>
<button name="refused" string="Refuse" states="hr_manager" type="object"
states="direct_manager" groups="employee_custody_request.group_general_manager"/>
<button name="refused" string="Refuse" states="direct_manager" type="object"
class="oe_highlight" groups="employee_custody_request.group_general_manager"/>
<button name="financialApproval" string="Financial Approval"
class="oe_highlight" type="object"
groups="employee_custody_request.group_account_manager"
attrs="{'invisible': ['|',
('is_financial_impact', '!=', True),
('state', '!=', 'executive_manager')]}"/>
<!-- <button name="financialApproval" string="Financial Approval"-->
<!-- class="oe_highlight" type="object"-->
<!-- -->
<!-- groups="employee_custody_request.group_account_manager"-->
<!-- attrs="{'invisible': [-->
<!-- ('state', '!=', 'executive_manager')]}"/>-->
<button name="pay" string="Transfer"
class="oe_highlight" type="object"
context="{'action_type': 'create'}"
groups="employee_custody_request.group_account_manager"
attrs="{'invisible': ['|',
('is_financial_impact', '!=', False),
attrs="{'invisible': [
('state', '!=', 'executive_manager')]}"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,submit,direct_manager,hr_manager,executive_manager,pay,financial_approve,refused"/>
statusbar_visible="draft,submit,direct_manager,executive_manager,pay,refused"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
@ -108,31 +131,46 @@
string="Go to account payment"
class="oe_stat_button" icon="fa-bars"
attrs="{'invisible': [('state', '!=', 'pay')]}"/>
<button name="action_open_refund_payments" type="object"
string="Show Refunds"
class="oe_stat_button" icon="fa-bars"
attrs="{'invisible': [('state', '!=', 'pay')]}" />
</div>
<group>
<group>
<field name="is_financial_impact" invisible="1"/>
<!-- <field name="is_financial_impact" invisible="1"/>-->
<field name="code" string="Code" readonly="1"/>
<field name="from_hr_depart" string="Another Employee"
attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="employee_id" string="Employee Name"
attrs="{'readonly':['|',('state','!=','draft'),('from_hr_depart','=',False)],'required':True}"/>
<field name="date"/>
<field name="date" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="company_id"/>
</group>
<group>
<field name="emp_expect_amount" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="custody_type_id"/>
<field name="custody_type_id" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="journal_id" attrs="{'invisible': [('id', '!=', 0)]}"/>
<field name="department_id"/>
<field name="department_id" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="job_id"/>
<field name="description"/>
<field name="description"
attrs="{
'required': [('state', '=', 'draft')],
'readonly': [('state', '!=', 'draft')]
}"/>
<field name="spent_amount" readonly="1"/>
<field name="custody_status" invisible="1"/>
<field name="remaining_amount"/>
<field name="permanent_pledge" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="total_paid_amount"/>
<field name="total_refunded_amount"/>
<field name="spent_amount_computed" invisible="1"/>
</group>
@ -154,7 +192,9 @@
<!-- <field name="from_hr_depart"/>-->
<!-- <field name="date"/>-->
<field name="employee_id"/>
<field name="emp_expect_amount"/>
<field name="total_paid_amount"/>
<field name="spent_amount" readonly="1"/>
<field name="remaining_amount"/>
</tree>
</field>
</record>
@ -171,6 +211,29 @@
</tree>
</field>
</record>
<record id="view_move_form_visibility_inherit" model="ir.ui.view">
<field name="name">account.move.petty.fields.visibility.inherit</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="petty_invoice.view_move_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='is_petty_paid']" position="attributes">
<attribute name="attrs">
{'invisible': [('move_type', 'not in', ('in_invoice','in_receipt'))]}
</attribute>
</xpath>
<xpath expr="//field[@name='petty_employee_id']" position="attributes">
<attribute name="attrs">
{'invisible': [('move_type', 'not in', ('in_invoice','in_receipt'))]}
</attribute>
</xpath>
</field>
</record>
<record id="action_employee_custody_report" model="ir.actions.server">
<field name="name">Employee Account</field>
<field name="model_id" ref="model_account_move_line"/>
@ -184,11 +247,11 @@
<field name="view_mode">tree,form</field>
</record>
<menuitem id="job_request_pledge" name="Request pledge"
action="job_request_action_pledge" parent="hr_expense.menu_hr_expense_my_expenses" sequence="10"
action="job_request_action_pledge" parent="account.menu_finance_payables" sequence="4"
groups="employee_custody_request.group_general_ledger_manger"/>
<menuitem id="employee_custody_report_menu" name="Employee Account Report"
action="action_employee_custody_report" parent="hr_expense.menu_hr_expense_report" sequence="20"
action="action_employee_custody_report" parent="account.menu_finance_payables" sequence="4"
groups="employee_custody_request.group_employee_custody_report"/>
</data>

View File

@ -1 +1,2 @@
from . import account_payment_register
from . import hr_request_pledge_confirm_wizard

View File

@ -150,10 +150,6 @@ class AccountPaymentRegister(models.TransientModel):
payments = self._create_payments()
if self._context.get('dont_redirect_to_payments'):
return True
# Update `move_id` values to include `takaful_sponsorship_id`
# if self.is_refund_sponsorship:
# for payment in payments:

View File

@ -0,0 +1,18 @@
from odoo import models, fields, api, _
class HrRequestPledgeConfirmWizard(models.TransientModel):
_name = 'hr.request.pledge.confirm.wizard'
_description = 'Confirmation Wizard for Pledge Payment'
pledge_id = fields.Many2one('hr.request.pledge', string='Pledge', required=True)
action_type = fields.Char(string="Action Type")
def action_confirm(self):
"""Execute the pay() on pledge after confirmation"""
self.ensure_one()
pledge = self.pledge_id
context = dict(self.env.context or {})
context['action_type'] = self.action_type
result = pledge.with_context(context).pay()
return result

View File

@ -0,0 +1,19 @@
<odoo>
<record id="view_hr_request_pledge_confirm_wizard_form" model="ir.ui.view">
<field name="name">hr.request.pledge.confirm.wizard.form</field>
<field name="model">hr.request.pledge.confirm.wizard</field>
<field name="arch" type="xml">
<form string="Confirm Pledge Action">
<group>
<div class="o_form_label text-bold">
Are you sure you want to feed the pledge?
</div>
</group>
<footer>
<button name="action_confirm" string="Confirm" type="object" class="oe_highlight"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>