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')]} + + + +