diff --git a/odex25_hr/exp_payroll_albilad/__init__.py b/odex25_hr/exp_payroll_albilad/__init__.py
new file mode 100644
index 000000000..0f7777525
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from . import models
+from . import wizard
+from . import report
diff --git a/odex25_hr/exp_payroll_albilad/__manifest__.py b/odex25_hr/exp_payroll_albilad/__manifest__.py
new file mode 100644
index 000000000..ce95a48c5
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/__manifest__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': 'HR Payroll Al-Bilad Bank',
+ 'version': '1.0',
+ 'category': 'Odex25-HR/Odex25-HR',
+ 'sequence': 5,
+ 'website': 'http://exp-sa.com',
+ 'license': 'GPL-3',
+ 'author': 'Expert Co. Ltd.',
+ 'summary': 'Al-Bilad Bank Payroll Report Extension',
+ 'description': """
+ Extends payroll module to add Al-Bilad Bank report format.
+ """,
+ 'depends': [
+ 'exp_payroll_custom',
+ ],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'wizard/payroll_bank_report_view.xml',
+ 'report/payroll_bank_report_views.xml',
+ 'report/payroll_bank_albilad_pdf_template.xml',
+ ],
+ 'installable': True,
+ 'auto_install': False,
+ 'application': False,
+}
diff --git a/odex25_hr/exp_payroll_albilad/models/__init__.py b/odex25_hr/exp_payroll_albilad/models/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/models/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/odex25_hr/exp_payroll_albilad/report/__init__.py b/odex25_hr/exp_payroll_albilad/report/__init__.py
new file mode 100644
index 000000000..dfe556cf2
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/report/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import payroll_bank_albilad_report
diff --git a/odex25_hr/exp_payroll_albilad/report/payroll_bank_albilad_pdf_template.xml b/odex25_hr/exp_payroll_albilad/report/payroll_bank_albilad_pdf_template.xml
new file mode 100644
index 000000000..6890e0583
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/report/payroll_bank_albilad_pdf_template.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+ Al-Bilad Bank Payroll Report (PDF)
+ payroll.bank.wiz
+ qweb-pdf
+ exp_payroll_albilad.albilad_bank_pdf
+ exp_payroll_albilad.albilad_bank_pdf
+ 'Al-Bilad Bank Report - %s' % (object.date_from)
+
+ report
+
+
diff --git a/odex25_hr/exp_payroll_albilad/report/payroll_bank_albilad_report.py b/odex25_hr/exp_payroll_albilad/report/payroll_bank_albilad_report.py
new file mode 100644
index 000000000..0958928d1
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/report/payroll_bank_albilad_report.py
@@ -0,0 +1,649 @@
+# -*- coding: utf-8 -*-
+from odoo import models, api
+
+
+class PayrollBankAlbiladReportXlsx(models.AbstractModel):
+ _name = "report.exp_payroll_albilad.payroll_bank_albilad_xlsx"
+ _inherit = 'report.report_xlsx.abstract'
+ _description = 'Al-Bilad Bank Payroll Report XLSX'
+
+ @api.model
+ def generate_xlsx_report(self, workbook, data, payslips):
+ emp_ids = data['employees']
+ bank_ids = data['banks']
+ salary_ids = data['salary']
+ date_from = data['date_from']
+ date_to = data['date_to']
+ no_details = data['no_details']
+ report_type = data['report_type']
+ entry_type = data['entry_type']
+ bank_type = data['bank_type']
+ company_id = self.sudo().env['res.company'].browse(data['company_id'])
+
+ employees = self.sudo().env['hr.employee'].search([('id', 'in', emp_ids)])
+ banks = self.sudo().env['res.bank'].search([('id', 'in', bank_ids)])
+ salary = self.sudo().env['hr.payroll.structure'].search([('id', 'in', salary_ids)])
+
+ # Get branch setting
+ branch_id = self.env['ir.config_parameter'].sudo().get_param('exp_payroll_custom.branch')
+ branch = True if branch_id == 'True' else False
+
+ sheet = workbook.add_worksheet('Al-Bilad Bank Report')
+
+ # Define formats
+ format1 = workbook.add_format({
+ 'bottom': True, 'right': True, 'left': True, 'top': True,
+ 'align': 'center', 'valign': 'vcenter'
+ })
+ format2 = workbook.add_format({
+ 'font_size': 11, 'bottom': True, 'right': True, 'left': True, 'top': True,
+ 'align': 'center', 'bold': True, 'valign': 'vcenter',
+ 'bg_color': 'blue', 'font_color': 'white'
+ })
+ format3 = workbook.add_format({
+ 'font_size': 14, 'bottom': True, 'right': True, 'left': True, 'top': True,
+ 'align': 'center', 'bold': True, 'valign': 'vcenter',
+ 'bg_color': '#FFA500'
+ })
+ format4 = workbook.add_format({
+ 'font_size': 11, 'bottom': True, 'right': True, 'left': True, 'top': True,
+ 'align': 'center', 'bold': True, 'valign': 'vcenter'
+ })
+
+ # Set column widths
+ sheet.set_column('A:A', 5)
+ sheet.set_column('B:B', 5)
+ sheet.set_column('C:L', 20)
+
+ # Add payroll header with date range and blue background
+ if report_type == 'salary':
+ sheet.merge_range('E3:H3', 'مسير البنك للرواتب' + ' ' + str(date_from) + ' - ' + str(date_to), format2)
+ elif report_type == 'allowance':
+ sheet.merge_range('E3:H3', 'مسير البنك للحوافز' + ' ' + str(date_from) + ' - ' + str(date_to), format2)
+ elif report_type == 'overtime':
+ sheet.merge_range('E3:H3', 'مسير البنك للعمل الإضافي' + ' ' + str(date_from) + ' - ' + str(date_to), format2)
+ elif report_type == 'training':
+ sheet.merge_range('E3:H3', 'مسير البنك للتدريب' + ' ' + str(date_from) + ' - ' + str(date_to), format2)
+ elif report_type == 'mission':
+ sheet.merge_range('E3:H3', 'مسير البنك لمهام العمل' + ' ' + str(date_from) + ' - ' + str(date_to), format2)
+
+ row = 4
+
+ if no_details:
+ # With details section
+ for bank in banks:
+ row += 3
+ sheet.write(row - 1, 1, bank.name, format3)
+
+
+ sheet.write(row, 2, 'Total Amount', format2)
+ sheet.write(row, 3, 'Beneficiary Account IBAN', format2)
+ sheet.write(row, 4, 'Beneficiary Name', format2)
+ sheet.write(row, 5, 'Beneficiary Bank CODE', format2)
+ sheet.write(row, 6, 'Payment Description', format2)
+ sheet.write(row, 7, 'Basic Salary', format2)
+ sheet.write(row, 8, 'Housing Allowance', format2)
+ sheet.write(row, 9, 'Other Earnings', format2)
+ sheet.write(row, 10, 'Deductions', format2)
+ sheet.write(row, 11, 'Beneficiary National/IqamaID', format2)
+ row += 1
+
+
+ sheet.write(row, 2, 'المبلغ الاجمالي', format2)
+ sheet.write(row, 3, 'رقم الايبان', format2)
+ sheet.write(row, 4, 'اسم المستفيد', format2)
+ sheet.write(row, 5, 'بنك المستفيد', format2)
+ sheet.write(row, 6, 'تفاصيل التحويل', format2)
+ sheet.write(row, 7, 'الراتب الاساسي', format2)
+ sheet.write(row, 8, 'بدل السكن', format2)
+ sheet.write(row, 9, 'البدلات الاخرى', format2)
+ sheet.write(row, 10, 'الخصم', format2)
+ sheet.write(row, 11, 'رقم الهوية', format2)
+
+ if report_type == 'salary':
+ payslip_ids = self._get_payslips(date_from, date_to, entry_type, employees, salary, salary_ids, bank)
+
+ if payslip_ids:
+ # Get salary rules
+ salary_rules = self.sudo().env['hr.salary.rule'].search([]).sorted(
+ key=lambda v: v.sequence).ids
+ payslip_line_obj = self.sudo().env['hr.payslip.line']
+
+ for payslip in payslip_ids:
+ basic = 0.0
+ housing = 0.0
+ payslip_lines_ids = payslip_line_obj.sudo().search([('slip_id', '=', payslip.id)])
+
+ if not payslip_lines_ids:
+ continue
+
+ for payslip_line_rec in payslip_lines_ids:
+ if payslip_line_rec.salary_rule_id.id in salary_rules:
+ if payslip_line_rec.salary_rule_id.rules_type == 'salary':
+ basic += payslip_line_rec.total
+ elif payslip_line_rec.salary_rule_id.rules_type == 'house':
+ housing += payslip_line_rec.total
+
+ other = round((payslip.total_allowances - basic - housing), 2)
+ data_list = [
+ payslip.employee_id.emp_no,
+ payslip.employee_id.name or ' ',
+ payslip.employee_id.bank_account_id.acc_number or ' ',
+ payslip.employee_id.bank_account_id.bank_id.bic,
+ payslip.total_sum,
+ payslip.employee_id.saudi_number.saudi_id if payslip.employee_id.check_nationality == True else payslip.employee_id.iqama_number.iqama_id,
+ basic, housing, other, round((payslip.total_deductions + payslip.total_loans), 2),
+ payslip.employee_id.branch_id.name if branch else payslip.employee_id.working_location.name,
+ company_id.currency_id.name, 'Active'
+ ]
+
+ row += 1
+ # Write AlBilad format data
+ sheet.write(row, 2, data_list[4], format1)
+ sheet.write(row, 3, data_list[2], format1)
+ sheet.write(row, 4, data_list[1], format1)
+ sheet.write(row, 5, data_list[3], format1)
+ sheet.write(row, 6, '', format1)
+ sheet.write(row, 7, data_list[6], format1)
+ sheet.write(row, 8, data_list[7], format1)
+ sheet.write(row, 9, data_list[8], format1)
+ sheet.write(row, 10, data_list[9], format1)
+ sheet.write(row, 11, data_list[5], format1)
+
+ elif report_type == 'allowance':
+ row = self._write_allowance_data(sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1)
+
+ elif report_type == 'overtime':
+ row = self._write_overtime_data(sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1)
+
+ elif report_type == 'mission':
+ row = self._write_mission_data(sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1)
+
+ elif report_type == 'training':
+ row = self._write_training_data(sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1)
+ else:
+ # No details section
+ row = 4
+ # English headers row
+ sheet.write(3, 2, 'Total Amount', format2)
+ sheet.write(3, 3, 'Beneficiary Account IBAN', format2)
+ sheet.write(3, 4, 'Beneficiary Name', format2)
+ sheet.write(3, 5, 'Beneficiary Bank CODE', format2)
+ sheet.write(3, 6, 'Payment Description', format2)
+ sheet.write(3, 7, 'Basic Salary', format2)
+ sheet.write(3, 8, 'Housing Allowance', format2)
+ sheet.write(3, 9, 'Other Earnings', format2)
+ sheet.write(3, 10, 'Deductions', format2)
+ sheet.write(3, 11, 'Beneficiary National/IqamaID', format2)
+
+ # Arabic headers row
+ sheet.write(4, 2, 'المبلغ الاجمالي', format2)
+ sheet.write(4, 3, 'رقم الايبان', format2)
+ sheet.write(4, 4, 'اسم المستفيد', format2)
+ sheet.write(4, 5, 'بنك المستفيد', format2)
+ sheet.write(4, 6, 'تفاصيل التحويل', format2)
+ sheet.write(4, 7, 'الراتب الاساسي', format2)
+ sheet.write(4, 8, 'بدل السكن', format2)
+ sheet.write(4, 9, 'البدلات الاخرى', format2)
+ sheet.write(4, 10, 'الخصم', format2)
+ sheet.write(4, 11, 'رقم الهوية', format2)
+
+ if report_type == 'salary':
+ for bank in banks:
+ payslip_ids = self._get_payslips(date_from, date_to, entry_type, employees, salary, salary_ids, bank)
+
+ if payslip_ids:
+ salary_rules = self.sudo().env['hr.salary.rule'].search([]).sorted(
+ key=lambda v: v.sequence).ids
+ payslip_line_obj = self.sudo().env['hr.payslip.line']
+
+ for payslip in payslip_ids:
+ basic = 0.0
+ housing = 0.0
+ payslip_lines_ids = payslip_line_obj.sudo().search([('slip_id', '=', payslip.id)])
+
+ if not payslip_lines_ids:
+ continue
+
+ for payslip_line_rec in payslip_lines_ids:
+ if payslip_line_rec.salary_rule_id.id in salary_rules:
+ if payslip_line_rec.salary_rule_id.rules_type == 'salary':
+ basic += payslip_line_rec.total
+ elif payslip_line_rec.salary_rule_id.rules_type == 'house':
+ housing += payslip_line_rec.total
+
+ other = round((payslip.total_allowances - basic - housing), 2)
+ data_list = [
+ payslip.employee_id.emp_no,
+ payslip.employee_id.name or ' ',
+ payslip.employee_id.bank_account_id.acc_number or ' ',
+ payslip.employee_id.bank_account_id.bank_id.bic,
+ payslip.total_sum,
+ payslip.employee_id.saudi_number.saudi_id if payslip.employee_id.check_nationality == True else payslip.employee_id.iqama_number.iqama_id,
+ basic, housing, other, round((payslip.total_deductions + payslip.total_loans), 2),
+ payslip.employee_id.branch_id.name if branch else payslip.employee_id.working_location.name,
+ company_id.currency_id.name, 'Active'
+ ]
+
+ row += 1
+ sheet.write(row, 2, data_list[4], format1)
+ sheet.write(row, 3, data_list[2], format1)
+ sheet.write(row, 4, data_list[1], format1)
+ sheet.write(row, 5, data_list[3], format1)
+ sheet.write(row, 6, '', format1)
+ sheet.write(row, 7, data_list[6], format1)
+ sheet.write(row, 8, data_list[7], format1)
+ sheet.write(row, 9, data_list[8], format1)
+ sheet.write(row, 10, data_list[9], format1)
+ sheet.write(row, 11, data_list[5], format1)
+
+ def _get_payslips(self, date_from, date_to, entry_type, employees, salary, salary_ids, bank):
+ domain_base = [
+ ('date_from', '>=', date_from),
+ ('date_to', '<=', date_to),
+ ('payslip_run_id.state', 'in', ['confirmed', 'transfered']),
+ ('employee_id.bank_account_id.bank_id', '=', bank.id)
+ ]
+
+ if entry_type == 'all':
+ domain = domain_base.copy()
+ if employees:
+ domain.append(('employee_id', 'in', employees.ids))
+ elif salary:
+ domain.append(('struct_id', 'in', salary_ids))
+ elif entry_type == 'posted':
+ domain = domain_base.copy()
+ if employees:
+ domain.extend([
+ ('employee_id', 'in', employees.ids),
+ '|',
+ ('move_id.state', '=', 'posted'),
+ ('payslip_run_id.move_id.state', '=', 'posted')
+ ])
+ elif salary:
+ domain.extend([
+ ('struct_id', 'in', salary_ids),
+ '|',
+ ('move_id.state', '=', 'posted'),
+ ('payslip_run_id.move_id.state', '=', 'posted')
+ ])
+ elif entry_type == 'unposted':
+ domain = domain_base.copy()
+ if employees:
+ domain.extend([
+ ('employee_id', 'in', employees.ids),
+ ('move_id.state', '!=', 'posted'),
+ ('payslip_run_id.move_id.state', '!=', 'posted')
+ ])
+ elif salary:
+ domain.extend([
+ ('struct_id', 'in', salary_ids),
+ ('move_id.state', '!=', 'posted'),
+ ('payslip_run_id.move_id.state', '!=', 'posted')
+ ])
+ else:
+ domain = domain_base
+
+ return self.sudo().env['hr.payslip'].search(domain)
+
+ def _write_allowance_data(self, sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1):
+ payslip_ids = self._get_payslips(date_from, date_to, entry_type, employees, salary, salary_ids, bank)
+
+ if payslip_ids:
+ for payslip in payslip_ids:
+ data_list = [
+ payslip.employee_id.emp_no,
+ payslip.employee_id.name or ' ',
+ payslip.employee_id.bank_account_id.acc_number or ' ',
+ payslip.employee_id.bank_account_id.bank_id.bic,
+ payslip.total_sum,
+ payslip.employee_id.saudi_number.saudi_id if payslip.employee_id.check_nationality else payslip.employee_id.iqama_number.iqama_id,
+ payslip.total_allowances, 0.0, 0.0, round((payslip.total_deductions + payslip.total_loans), 2),
+ ]
+
+ row += 1
+ sheet.write(row, 2, data_list[4], format1)
+ sheet.write(row, 3, data_list[2], format1)
+ sheet.write(row, 4, data_list[1], format1)
+ sheet.write(row, 5, data_list[3], format1)
+ sheet.write(row, 6, '', format1)
+ sheet.write(row, 7, data_list[6], format1)
+ sheet.write(row, 8, data_list[7], format1)
+ sheet.write(row, 9, data_list[8], format1)
+ sheet.write(row, 10, data_list[9], format1)
+ sheet.write(row, 11, data_list[5], format1)
+
+ return row
+
+ def _write_overtime_data(self, sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1):
+ payslip_ids = self._get_payslips(date_from, date_to, entry_type, employees, salary, salary_ids, bank)
+
+ if payslip_ids:
+ for payslip in payslip_ids:
+ data_list = [
+ payslip.employee_id.emp_no,
+ payslip.employee_id.name or ' ',
+ payslip.employee_id.bank_account_id.acc_number or ' ',
+ payslip.employee_id.bank_account_id.bank_id.bic,
+ payslip.total_sum,
+ payslip.employee_id.saudi_number.saudi_id if payslip.employee_id.check_nationality else payslip.employee_id.iqama_number.iqama_id,
+ 0.0, 0.0, payslip.total_allowances, round((payslip.total_deductions + payslip.total_loans), 2),
+ ]
+
+ row += 1
+ sheet.write(row, 2, data_list[4], format1)
+ sheet.write(row, 3, data_list[2], format1)
+ sheet.write(row, 4, data_list[1], format1)
+ sheet.write(row, 5, data_list[3], format1)
+ sheet.write(row, 6, '', format1)
+ sheet.write(row, 7, data_list[6], format1)
+ sheet.write(row, 8, data_list[7], format1)
+ sheet.write(row, 9, data_list[8], format1)
+ sheet.write(row, 10, data_list[9], format1)
+ sheet.write(row, 11, data_list[5], format1)
+
+ return row
+
+ def _write_mission_data(self, sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1):
+ """Write mission data"""
+ payslip_ids = self._get_payslips(date_from, date_to, entry_type, employees, salary, salary_ids, bank)
+
+ if payslip_ids:
+ for payslip in payslip_ids:
+ # For missions, show in "Other Earnings" column
+ data_list = [
+ payslip.employee_id.emp_no,
+ payslip.employee_id.name or ' ',
+ payslip.employee_id.bank_account_id.acc_number or ' ',
+ payslip.employee_id.bank_account_id.bank_id.bic,
+ payslip.total_sum,
+ payslip.employee_id.saudi_number.saudi_id if payslip.employee_id.check_nationality else payslip.employee_id.iqama_number.iqama_id,
+ 0.0, 0.0, payslip.total_allowances, round((payslip.total_deductions + payslip.total_loans), 2),
+ ]
+
+ row += 1
+ sheet.write(row, 2, data_list[4], format1)
+ sheet.write(row, 3, data_list[2], format1)
+ sheet.write(row, 4, data_list[1], format1)
+ sheet.write(row, 5, data_list[3], format1)
+ sheet.write(row, 6, '', format1)
+ sheet.write(row, 7, data_list[6], format1)
+ sheet.write(row, 8, data_list[7], format1)
+ sheet.write(row, 9, data_list[8], format1)
+ sheet.write(row, 10, data_list[9], format1)
+ sheet.write(row, 11, data_list[5], format1)
+
+ return row
+
+ def _write_training_data(self, sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1):
+ payslip_ids = self._get_payslips(date_from, date_to, entry_type, employees, salary, salary_ids, bank)
+
+ if payslip_ids:
+ for payslip in payslip_ids:
+ data_list = [
+ payslip.employee_id.emp_no,
+ payslip.employee_id.name or ' ',
+ payslip.employee_id.bank_account_id.acc_number or ' ',
+ payslip.employee_id.bank_account_id.bank_id.bic,
+ payslip.total_sum,
+ payslip.employee_id.saudi_number.saudi_id if payslip.employee_id.check_nationality else payslip.employee_id.iqama_number.iqama_id,
+ 0.0, 0.0, payslip.total_allowances, round((payslip.total_deductions + payslip.total_loans), 2),
+ ]
+
+ row += 1
+ sheet.write(row, 2, data_list[4], format1)
+ sheet.write(row, 3, data_list[2], format1)
+ sheet.write(row, 4, data_list[1], format1)
+ sheet.write(row, 5, data_list[3], format1)
+ sheet.write(row, 6, '', format1)
+ sheet.write(row, 7, data_list[6], format1)
+ sheet.write(row, 8, data_list[7], format1)
+ sheet.write(row, 9, data_list[8], format1)
+ sheet.write(row, 10, data_list[9], format1)
+ sheet.write(row, 11, data_list[5], format1)
+
+ return row
+
+
+class HrPayslipExtended(models.Model):
+ _inherit = 'hr.payslip'
+
+ @api.model
+ def search(self, args, offset=0, limit=None, order=None, count=False):
+
+ if self.env.context.get('use_payslip_run_confirmed_state'):
+ modified_args = []
+ for item in args:
+ if isinstance(item, (tuple, list)) and len(item) == 3:
+ field, operator, value = item
+ # Replace state = 'transfered' with payslip_run_id.state = 'confirmed'
+ if field == 'state' and operator == '=' and value == 'transfered':
+ modified_args.append(('payslip_run_id.state', '=', 'confirmed'))
+ else:
+ modified_args.append(item)
+ else:
+ modified_args.append(item)
+ args = modified_args
+
+ return super(HrPayslipExtended, self).search(args, offset=offset, limit=limit, order=order, count=count)
+
+
+class PayrollXlsxExtended(models.AbstractModel):
+ _inherit = 'report.exp_payroll_custom.report_payroll_bank_xlsx'
+
+ @api.model
+ def generate_xlsx_report(self, workbook, data, payslips):
+ return super(PayrollXlsxExtended, self.with_context(use_payslip_run_confirmed_state=True)).generate_xlsx_report(workbook, data, payslips)
+
+
+class PayrollBankAlbiladReportPdf(models.AbstractModel):
+ _name = "report.exp_payroll_albilad.albilad_bank_pdf"
+ _description = 'Al-Bilad Bank Payroll Report PDF'
+
+ @api.model
+ def _get_report_values(self, docids, data=None):
+ if not data:
+ return {}
+
+ emp_ids = data.get('employees', [])
+ bank_ids = data.get('banks', [])
+ salary_ids = data.get('salary', [])
+ date_from = data.get('date_from')
+ date_to = data.get('date_to')
+ no_details = data.get('no_details', False)
+ report_type = data.get('report_type', 'salary')
+ entry_type = data.get('entry_type', 'all')
+ company_id = self.env['res.company'].browse(data.get('company_id'))
+
+ employees = self.env['hr.employee'].search([('id', 'in', emp_ids)])
+ banks = self.env['res.bank'].search([('id', 'in', bank_ids)])
+ salary = self.env['hr.payroll.structure'].search([('id', 'in', salary_ids)])
+
+ # Get branch setting
+ branch_id = self.env['ir.config_parameter'].sudo().get_param('exp_payroll_custom.branch')
+ branch = True if branch_id == 'True' else False
+
+ # Get report title based on type (Arabic)
+ report_titles = {
+ 'salary': 'مسير البنك للرواتب',
+ 'allowance': 'مسير البنك للحوافز',
+ 'overtime': 'مسير البنك للعمل الإضافي',
+ 'training': 'مسير البنك للتدريب',
+ 'mission': 'مسير البنك لمهام العمل',
+ }
+ report_title = report_titles.get(report_type, 'مسير البنك للرواتب')
+
+ payslip_data = []
+
+ if no_details:
+ # Group by bank
+ for bank in banks:
+ payslip_ids = self._get_payslips(date_from, date_to, entry_type, employees, salary, salary_ids, bank)
+ bank_payslips = []
+
+ if payslip_ids:
+ for payslip in payslip_ids:
+ payslip_dict = self._prepare_payslip_data(payslip, report_type)
+ bank_payslips.append(payslip_dict)
+
+ payslip_data.append({
+ 'bank_name': bank.name,
+ 'payslips': bank_payslips
+ })
+ else:
+ # No grouping - all payslips in one list
+ all_payslips = []
+ for bank in banks:
+ payslip_ids = self._get_payslips(date_from, date_to, entry_type, employees, salary, salary_ids, bank)
+
+ if payslip_ids:
+ for payslip in payslip_ids:
+ payslip_dict = self._prepare_payslip_data(payslip, report_type)
+ all_payslips.append(payslip_dict)
+
+ payslip_data = all_payslips
+
+ date_from_str = str(date_from) if date_from else ''
+ date_to_str = str(date_to) if date_to else ''
+
+ return {
+ 'doc_ids': docids,
+ 'doc_model': 'payroll.bank.wiz',
+ 'docs': self.env['payroll.bank.wiz'].browse(docids),
+ 'date_from': date_from_str,
+ 'date_to': date_to_str,
+ 'no_details': no_details,
+ 'report_type': report_type,
+ 'report_title': report_title,
+ 'payslip_data': payslip_data,
+ 'company': company_id,
+ }
+
+ def _prepare_payslip_data(self, payslip, report_type):
+ """Prepare payslip data dictionary based on report type"""
+ # Get national ID safely
+ national_id = ''
+ try:
+ if hasattr(payslip.employee_id, 'check_nationality') and payslip.employee_id.check_nationality:
+ if hasattr(payslip.employee_id, 'saudi_number') and payslip.employee_id.saudi_number:
+ national_id = payslip.employee_id.saudi_number.saudi_id or ''
+ else:
+ if hasattr(payslip.employee_id, 'iqama_number') and payslip.employee_id.iqama_number:
+ national_id = payslip.employee_id.iqama_number.iqama_id or ''
+ except:
+ national_id = ''
+
+ iban = ' '
+ bank_code = ' '
+ if payslip.employee_id.bank_account_id:
+ iban = payslip.employee_id.bank_account_id.acc_number or ' '
+ if payslip.employee_id.bank_account_id.bank_id:
+ bank_code = payslip.employee_id.bank_account_id.bank_id.bic or ' '
+
+ if report_type == 'salary':
+ salary_rules = self.env['hr.salary.rule'].search([]).sorted(key=lambda v: v.sequence).ids
+ payslip_line_obj = self.env['hr.payslip.line']
+
+ basic = 0.0
+ housing = 0.0
+ payslip_lines_ids = payslip_line_obj.search([('slip_id', '=', payslip.id)])
+
+ for payslip_line_rec in payslip_lines_ids:
+ if payslip_line_rec.salary_rule_id.id in salary_rules:
+ if payslip_line_rec.salary_rule_id.rules_type == 'salary':
+ basic += payslip_line_rec.total
+ elif payslip_line_rec.salary_rule_id.rules_type == 'house':
+ housing += payslip_line_rec.total
+
+ other = round((payslip.total_allowances - basic - housing), 2)
+
+ return {
+ 'total_amount': payslip.total_sum or 0.0,
+ 'iban': iban,
+ 'name': payslip.employee_id.name or ' ',
+ 'bank_code': bank_code,
+ 'description': '',
+ 'basic_salary': basic,
+ 'housing': housing,
+ 'other': other,
+ 'deductions': round((payslip.total_deductions + payslip.total_loans), 2),
+ 'national_id': national_id,
+ }
+ elif report_type == 'allowance':
+ # For allowances, show total allowances as basic
+ return {
+ 'total_amount': payslip.total_sum or 0.0,
+ 'iban': iban,
+ 'name': payslip.employee_id.name or ' ',
+ 'bank_code': bank_code,
+ 'description': '',
+ 'basic_salary': payslip.total_allowances or 0.0,
+ 'housing': 0.0,
+ 'other': 0.0,
+ 'deductions': round((payslip.total_deductions + payslip.total_loans), 2),
+ 'national_id': national_id,
+ }
+ elif report_type in ['overtime', 'mission', 'training']:
+ # For overtime, mission, training - show as other earnings
+ return {
+ 'total_amount': payslip.total_sum or 0.0,
+ 'iban': iban,
+ 'name': payslip.employee_id.name or ' ',
+ 'bank_code': bank_code,
+ 'description': '',
+ 'basic_salary': 0.0,
+ 'housing': 0.0,
+ 'other': payslip.total_allowances or 0.0,
+ 'deductions': round((payslip.total_deductions + payslip.total_loans), 2),
+ 'national_id': national_id,
+ }
+
+ def _get_payslips(self, date_from, date_to, entry_type, employees, salary, salary_ids, bank):
+ """Helper method to get payslips based on criteria"""
+ domain_base = [
+ ('date_from', '>=', date_from),
+ ('date_to', '<=', date_to),
+ ('payslip_run_id.state', 'in', ['confirmed', 'transfered']),
+ ('employee_id.bank_account_id.bank_id', '=', bank.id)
+ ]
+
+ if entry_type == 'all':
+ domain = domain_base.copy()
+ if employees:
+ domain.append(('employee_id', 'in', employees.ids))
+ elif salary:
+ domain.append(('struct_id', 'in', salary_ids))
+ elif entry_type == 'posted':
+ domain = domain_base.copy()
+ if employees:
+ domain.extend([
+ ('employee_id', 'in', employees.ids),
+ '|',
+ ('move_id.state', '=', 'posted'),
+ ('payslip_run_id.move_id.state', '=', 'posted')
+ ])
+ elif salary:
+ domain.extend([
+ ('struct_id', 'in', salary_ids),
+ '|',
+ ('move_id.state', '=', 'posted'),
+ ('payslip_run_id.move_id.state', '=', 'posted')
+ ])
+ elif entry_type == 'unposted':
+ domain = domain_base.copy()
+ if employees:
+ domain.extend([
+ ('employee_id', 'in', employees.ids),
+ ('move_id.state', '!=', 'posted'),
+ ('payslip_run_id.move_id.state', '!=', 'posted')
+ ])
+ elif salary:
+ domain.extend([
+ ('struct_id', 'in', salary_ids),
+ ('move_id.state', '!=', 'posted'),
+ ('payslip_run_id.move_id.state', '!=', 'posted')
+ ])
+ else:
+ domain = domain_base
+
+ return self.env['hr.payslip'].search(domain)
diff --git a/odex25_hr/exp_payroll_albilad/report/payroll_bank_report_views.xml b/odex25_hr/exp_payroll_albilad/report/payroll_bank_report_views.xml
new file mode 100644
index 000000000..fe23bae59
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/report/payroll_bank_report_views.xml
@@ -0,0 +1,12 @@
+
+
+
+ Al-Bilad Bank Payroll Report
+ payroll.bank.wiz
+ xlsx
+ exp_payroll_albilad.payroll_bank_albilad_xlsx
+ exp_payroll_albilad.payroll_bank_albilad_xlsx
+
+ report
+
+
diff --git a/odex25_hr/exp_payroll_albilad/security/ir.model.access.csv b/odex25_hr/exp_payroll_albilad/security/ir.model.access.csv
new file mode 100644
index 000000000..97dd8b917
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/security/ir.model.access.csv
@@ -0,0 +1 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
diff --git a/odex25_hr/exp_payroll_albilad/wizard/__init__.py b/odex25_hr/exp_payroll_albilad/wizard/__init__.py
new file mode 100644
index 000000000..7d819eda7
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/wizard/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import payroll_bank_report
diff --git a/odex25_hr/exp_payroll_albilad/wizard/payroll_bank_report.py b/odex25_hr/exp_payroll_albilad/wizard/payroll_bank_report.py
new file mode 100644
index 000000000..aa3d29c97
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/wizard/payroll_bank_report.py
@@ -0,0 +1,96 @@
+# -*- coding:utf-8 -*-
+
+from odoo import models, fields, api, _
+from odoo.exceptions import UserError
+
+
+class BankPayslipReportAlbilad(models.TransientModel):
+ _inherit = 'payroll.bank.wiz'
+
+ bank_type = fields.Selection(
+ selection_add=[('albilad', 'Al-Bilad Bank')],
+ ondelete={'albilad': 'set default'}
+ )
+
+ def print_report(self):
+ if self.bank_type == 'albilad':
+ # Validate that banks are selected
+ if not self.bank_ids:
+ raise UserError(_('Please select at least one bank to generate the report.'))
+
+ [data] = self.read()
+ date_from = self.date_from
+ date_to = self.date_to
+ no_details = self.no_details
+ report_type = self.report_type
+ entry_type = self.entry_type
+ bank_type = self.bank_type
+ employees = self.env['hr.employee'].search([('id', 'in', self.employee_ids.ids)])
+ banks = self.env['res.bank'].search([('id', 'in', self.bank_ids.ids)])
+ salary = self.env['hr.payroll.structure'].search([('id', 'in', self.salary_ids.ids)])
+ company_id = self.env['res.company'].search([('id', '=', self.company_id.id)])
+
+ datas = {
+ 'employees': employees.ids,
+ 'banks': banks.ids,
+ 'salary': salary.ids,
+ 'form': data,
+ 'date_from': date_from,
+ 'date_to': date_to,
+ 'no_details': no_details,
+ 'report_type': report_type,
+ 'entry_type': entry_type,
+ 'bank_type': bank_type,
+ 'company_id': company_id.id,
+ }
+
+ return self.env.ref('exp_payroll_albilad.report_payroll_bank_albilad_xlsx').report_action(self, data=datas)
+ else:
+ return super(BankPayslipReportAlbilad, self).print_report()
+
+ def print_pdf_report(self):
+ if self.bank_type == 'albilad':
+ if not self.bank_ids:
+ raise UserError(_('Please select at least one bank to generate the report.'))
+
+ [data] = self.read()
+ date_from = self.date_from
+ date_to = self.date_to
+ no_details = self.no_details
+ report_type = self.report_type
+ entry_type = self.entry_type
+ bank_type = self.bank_type
+ employees = self.env['hr.employee'].search([('id', 'in', self.employee_ids.ids)])
+ banks = self.env['res.bank'].search([('id', 'in', self.bank_ids.ids)])
+ salary = self.env['hr.payroll.structure'].search([('id', 'in', self.salary_ids.ids)])
+ company_id = self.env['res.company'].search([('id', '=', self.company_id.id)])
+
+ datas = {
+ 'employees': employees.ids,
+ 'banks': banks.ids,
+ 'salary': salary.ids,
+ 'form': data,
+ 'date_from': date_from,
+ 'date_to': date_to,
+ 'no_details': no_details,
+ 'report_type': report_type,
+ 'entry_type': entry_type,
+ 'bank_type': bank_type,
+ 'company_id': company_id.id,
+ }
+
+ return self.env.ref('exp_payroll_albilad.report_payroll_bank_albilad_pdf').report_action(self, data=datas)
+ else:
+ # Call parent method for other bank types if it exists
+ if hasattr(super(BankPayslipReportAlbilad, self), 'print_pdf_report'):
+ return super(BankPayslipReportAlbilad, self).print_pdf_report()
+ else:
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'message': _('PDF report is only available for Al-Bilad bank type.'),
+ 'type': 'warning',
+ 'sticky': False,
+ }
+ }
diff --git a/odex25_hr/exp_payroll_albilad/wizard/payroll_bank_report_view.xml b/odex25_hr/exp_payroll_albilad/wizard/payroll_bank_report_view.xml
new file mode 100644
index 000000000..f881ed522
--- /dev/null
+++ b/odex25_hr/exp_payroll_albilad/wizard/payroll_bank_report_view.xml
@@ -0,0 +1,13 @@
+
+
+
+ payroll.bank.wiz.form.albilad
+ payroll.bank.wiz
+
+
+
+ {'required':[('bank_type','=','albilad')]}
+
+
+
+