diff --git a/odex25_accounting/employee_custody_request/__manifest__.py b/odex25_accounting/employee_custody_request/__manifest__.py index 9215f85ad..5e514c088 100644 --- a/odex25_accounting/employee_custody_request/__manifest__.py +++ b/odex25_accounting/employee_custody_request/__manifest__.py @@ -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', - ], + } diff --git a/odex25_accounting/employee_custody_request/i18n/ar_001.po b/odex25_accounting/employee_custody_request/i18n/ar_001.po index 7de679972..b82a76111 100644 --- a/odex25_accounting/employee_custody_request/i18n/ar_001.po +++ b/odex25_accounting/employee_custody_request/i18n/ar_001.po @@ -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 "عهده" \ No newline at end of file +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 "حساب الارجاع" diff --git a/odex25_accounting/employee_custody_request/models/account_journal.py b/odex25_accounting/employee_custody_request/models/account_journal.py index 63e534db5..02fea1239 100644 --- a/odex25_accounting/employee_custody_request/models/account_journal.py +++ b/odex25_accounting/employee_custody_request/models/account_journal.py @@ -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')) \ No newline at end of file diff --git a/odex25_accounting/employee_custody_request/models/account_payment.py b/odex25_accounting/employee_custody_request/models/account_payment.py index 1a86f988f..54c9a8c4e 100644 --- a/odex25_accounting/employee_custody_request/models/account_payment.py +++ b/odex25_accounting/employee_custody_request/models/account_payment.py @@ -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 diff --git a/odex25_accounting/employee_custody_request/models/hr_expense.py b/odex25_accounting/employee_custody_request/models/hr_expense.py index 61d33bbf5..d8c35b478 100644 --- a/odex25_accounting/employee_custody_request/models/hr_expense.py +++ b/odex25_accounting/employee_custody_request/models/hr_expense.py @@ -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 \ No newline at end of file diff --git a/odex25_accounting/employee_custody_request/models/hr_expense_sheet.py b/odex25_accounting/employee_custody_request/models/hr_expense_sheet.py index 196dcb6c7..9b2386160 100644 --- a/odex25_accounting/employee_custody_request/models/hr_expense_sheet.py +++ b/odex25_accounting/employee_custody_request/models/hr_expense_sheet.py @@ -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() \ No newline at end of file diff --git a/odex25_accounting/employee_custody_request/models/models.py b/odex25_accounting/employee_custody_request/models/models.py index 4b5369faa..5ec234c8c 100644 --- a/odex25_accounting/employee_custody_request/models/models.py +++ b/odex25_accounting/employee_custody_request/models/models.py @@ -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' diff --git a/odex25_accounting/employee_custody_request/models/types_custody.py b/odex25_accounting/employee_custody_request/models/types_custody.py index 1fbbd7da8..5e4caa397 100644 --- a/odex25_accounting/employee_custody_request/models/types_custody.py +++ b/odex25_accounting/employee_custody_request/models/types_custody.py @@ -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: diff --git a/odex25_accounting/employee_custody_request/security/ir.model.access.csv b/odex25_accounting/employee_custody_request/security/ir.model.access.csv index 0335fa9d1..40d92acdc 100644 --- a/odex25_accounting/employee_custody_request/security/ir.model.access.csv +++ b/odex25_accounting/employee_custody_request/security/ir.model.access.csv @@ -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 \ No newline at end of file diff --git a/odex25_accounting/employee_custody_request/security/security.xml b/odex25_accounting/employee_custody_request/security/security.xml index f53a74f01..3bf6fecb1 100644 --- a/odex25_accounting/employee_custody_request/security/security.xml +++ b/odex25_accounting/employee_custody_request/security/security.xml @@ -31,23 +31,44 @@ Officer + + HR User: Own Pledges + + + [('employee_id.user_id', '=', user.id)] + Loan Manager + + Administrator + General Manager + Accounting Manager + + All Pledges + + + [(1, '=', 1)] + + Employee Custody Report diff --git a/odex25_accounting/employee_custody_request/views/hr_expenes.xml b/odex25_accounting/employee_custody_request/views/hr_expenes.xml index 993959862..08c4b221f 100644 --- a/odex25_accounting/employee_custody_request/views/hr_expenes.xml +++ b/odex25_accounting/employee_custody_request/views/hr_expenes.xml @@ -4,11 +4,18 @@ hr.expense.sheet - - + + - + + + + 1 + + + 1 + diff --git a/odex25_accounting/employee_custody_request/views/types_custody.xml b/odex25_accounting/employee_custody_request/views/types_custody.xml index 3f325c58c..31bef43dd 100644 --- a/odex25_accounting/employee_custody_request/views/types_custody.xml +++ b/odex25_accounting/employee_custody_request/views/types_custody.xml @@ -30,6 +30,9 @@ + + + diff --git a/odex25_accounting/employee_custody_request/views/views.xml b/odex25_accounting/employee_custody_request/views/views.xml index 8c373605d..c01c2188e 100644 --- a/odex25_accounting/employee_custody_request/views/views.xml +++ b/odex25_accounting/employee_custody_request/views/views.xml @@ -50,8 +50,17 @@ class="oe_stat_button" icon="fa-bars" attrs="{'invisible': [('hr_request_pledge', '=', False)]}"/> --> + + + {'readonly': [('petty_cash_pledge', '=', True)]} + + + + + + @@ -62,45 +71,59 @@ + + + - - + + + + - + - + + + + + + + statusbar_visible="draft,submit,direct_manager,executive_manager,pay,refused"/> @@ -108,31 +131,46 @@ string="Go to account payment" class="oe_stat_button" icon="fa-bars" attrs="{'invisible': [('state', '!=', 'pay')]}"/> + + - + - + + - + - + - + + + + + + @@ -154,7 +192,9 @@ - + + + @@ -171,6 +211,29 @@ + + + + account.move.petty.fields.visibility.inherit + account.move + + + + + + {'invisible': [('move_type', 'not in', ('in_invoice','in_receipt'))]} + + + + + + {'invisible': [('move_type', 'not in', ('in_invoice','in_receipt'))]} + + + + + + Employee Account @@ -184,11 +247,11 @@ tree,form diff --git a/odex25_accounting/employee_custody_request/wizard/__init__.py b/odex25_accounting/employee_custody_request/wizard/__init__.py index 019698a88..6065b693b 100644 --- a/odex25_accounting/employee_custody_request/wizard/__init__.py +++ b/odex25_accounting/employee_custody_request/wizard/__init__.py @@ -1 +1,2 @@ from . import account_payment_register +from . import hr_request_pledge_confirm_wizard diff --git a/odex25_accounting/employee_custody_request/wizard/account_payment_register.py b/odex25_accounting/employee_custody_request/wizard/account_payment_register.py index a465d8ef4..f2e6c6e07 100644 --- a/odex25_accounting/employee_custody_request/wizard/account_payment_register.py +++ b/odex25_accounting/employee_custody_request/wizard/account_payment_register.py @@ -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: diff --git a/odex25_accounting/employee_custody_request/wizard/hr_request_pledge_confirm_wizard.py b/odex25_accounting/employee_custody_request/wizard/hr_request_pledge_confirm_wizard.py new file mode 100644 index 000000000..ffed737a2 --- /dev/null +++ b/odex25_accounting/employee_custody_request/wizard/hr_request_pledge_confirm_wizard.py @@ -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 + diff --git a/odex25_accounting/employee_custody_request/wizard/hr_request_pledge_confirm_wizard_view.xml b/odex25_accounting/employee_custody_request/wizard/hr_request_pledge_confirm_wizard_view.xml new file mode 100644 index 000000000..42b5c2dd4 --- /dev/null +++ b/odex25_accounting/employee_custody_request/wizard/hr_request_pledge_confirm_wizard_view.xml @@ -0,0 +1,19 @@ + + + hr.request.pledge.confirm.wizard.form + hr.request.pledge.confirm.wizard + + + + + Are you sure you want to feed the pledge? + + + + + + +