Create new type of payroll bank type

This commit is contained in:
Mazen Abdo 2025-10-30 01:00:11 +03:00
parent eaab462fed
commit f393f1614c
11 changed files with 987 additions and 0 deletions

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import models
from . import wizard
from . import report

View File

@ -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,
}

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import payroll_bank_albilad_report

View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Al-Bilad Bank PDF Report Template -->
<template id="albilad_bank_pdf">
<t t-call="web.html_container">
<!-- <t t-call="web.external_layout">-->
<div class="page" dir="rtl">
<style>
.albilad-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
font-size: 10pt;
}
.albilad-table th, .albilad-table td {
border: 1px solid #000;
padding: 6px 4px;
text-align: center;
vertical-align: middle;
}
.albilad-header {
background-color: #3e5d7f;
color: #ffffff;
font-weight: bold;
font-size: 10pt;
}
.albilad-bank-header {
background-color: #FFA500;
font-weight: bold;
font-size: 13pt;
text-align: center;
padding: 10px;
margin-top: 25px;
margin-bottom: 10px;
}
.albilad-title {
background-color: #3e5d7f;
color: #ffffff;
font-weight: bold;
font-size: 12pt;
text-align: center;
padding: 12px;
margin-bottom: 20px;
}
.albilad-data-row {
font-size: 9pt;
}
</style>
<!-- Title based on report type with date range -->
<div class="albilad-title">
<t t-esc="report_title"/> <t t-esc="date_from"/> - <t t-esc="date_to"/>
</div>
<!-- Check if grouped by bank (no_details = True) -->
<t t-if="no_details">
<!-- With details - grouped by bank -->
<t t-foreach="payslip_data" t-as="bank_data">
<div class="albilad-bank-header">
<t t-esc="bank_data.get('bank_name', '')"/>
</div>
<table class="albilad-table">
<thead>
<!-- Arabic headers -->
<tr class="albilad-header">
<th>المبلغ الاجمالي</th>
<th>رقم الايبان</th>
<th>اسم المستفيد</th>
<th>بنك المستفيد</th>
<th>تفاصيل التحويل</th>
<th>الراتب الاساسي</th>
<th>بدل السكن</th>
<th>البدلات الاخرى</th>
<th>الخصم</th>
<th>رقم الهوية</th>
</tr>
</thead>
<tbody>
<t t-foreach="bank_data.get('payslips', [])" t-as="payslip">
<tr class="albilad-data-row">
<td><t t-esc="payslip.get('total_amount', 0.0)"/></td>
<td><t t-esc="payslip.get('iban', '')"/></td>
<td><t t-esc="payslip.get('name', '')"/></td>
<td><t t-esc="payslip.get('bank_code', '')"/></td>
<td><t t-esc="payslip.get('description', '')"/></td>
<td><t t-esc="payslip.get('basic_salary', 0.0)"/></td>
<td><t t-esc="payslip.get('housing', 0.0)"/></td>
<td><t t-esc="payslip.get('other', 0.0)"/></td>
<td><t t-esc="payslip.get('deductions', 0.0)"/></td>
<td><t t-esc="payslip.get('national_id', '')"/></td>
</tr>
</t>
</tbody>
</table>
</t>
</t>
<!-- Without details - all data in one table -->
<t t-if="not no_details">
<table class="albilad-table">
<thead>
<!-- Arabic headers -->
<tr class="albilad-header">
<th>المبلغ الاجمالي</th>
<th>رقم الايبان</th>
<th>اسم المستفيد</th>
<th>بنك المستفيد</th>
<th>تفاصيل التحويل</th>
<th>الراتب الاساسي</th>
<th>بدل السكن</th>
<th>البدلات الاخرى</th>
<th>الخصم</th>
<th>رقم الهوية</th>
</tr>
</thead>
<tbody>
<t t-foreach="payslip_data" t-as="payslip">
<tr class="albilad-data-row">
<td><t t-esc="payslip.get('total_amount', 0.0)"/></td>
<td><t t-esc="payslip.get('iban', '')"/></td>
<td><t t-esc="payslip.get('name', '')"/></td>
<td><t t-esc="payslip.get('bank_code', '')"/></td>
<td><t t-esc="payslip.get('description', '')"/></td>
<td><t t-esc="payslip.get('basic_salary', 0.0)"/></td>
<td><t t-esc="payslip.get('housing', 0.0)"/></td>
<td><t t-esc="payslip.get('other', 0.0)"/></td>
<td><t t-esc="payslip.get('deductions', 0.0)"/></td>
<td><t t-esc="payslip.get('national_id', '')"/></td>
</tr>
</t>
</tbody>
</table>
</t>
</div>
<!-- </t>-->
</t>
</template>
<!-- Main Report Definition -->
<record id="report_payroll_bank_albilad_pdf" model="ir.actions.report">
<field name="name">Al-Bilad Bank Payroll Report (PDF)</field>
<field name="model">payroll.bank.wiz</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">exp_payroll_albilad.albilad_bank_pdf</field>
<field name="report_file">exp_payroll_albilad.albilad_bank_pdf</field>
<field name="print_report_name">'Al-Bilad Bank Report - %s' % (object.date_from)</field>
<field name="binding_model_id" ref="exp_payroll_custom.model_payroll_bank_wiz"/>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@ -0,0 +1,668 @@
# -*- 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)
# English headers row
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
# Arabic headers row
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) # Total Amount
sheet.write(row, 3, data_list[2], format1) # Beneficiary Account IBAN
sheet.write(row, 4, data_list[1], format1) # Beneficiary Name
sheet.write(row, 5, data_list[3], format1) # Beneficiary Bank CODE
sheet.write(row, 6, '', format1) # Payment Description (empty)
sheet.write(row, 7, data_list[6], format1) # Basic Salary
sheet.write(row, 8, data_list[7], format1) # Housing Allowance
sheet.write(row, 9, data_list[8], format1) # Other Earnings
sheet.write(row, 10, data_list[9], format1) # Deductions
sheet.write(row, 11, data_list[5], format1) # Beneficiary National/IqamaID
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
# Write AlBilad format data
sheet.write(row, 2, data_list[4], format1) # Total Amount
sheet.write(row, 3, data_list[2], format1) # Beneficiary Account IBAN
sheet.write(row, 4, data_list[1], format1) # Beneficiary Name
sheet.write(row, 5, data_list[3], format1) # Beneficiary Bank CODE
sheet.write(row, 6, '', format1) # Payment Description (empty)
sheet.write(row, 7, data_list[6], format1) # Basic Salary
sheet.write(row, 8, data_list[7], format1) # Housing Allowance
sheet.write(row, 9, data_list[8], format1) # Other Earnings
sheet.write(row, 10, data_list[9], format1) # Deductions
sheet.write(row, 11, data_list[5], format1) # Beneficiary National/IqamaID
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.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):
"""Write allowance 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 allowances, show total allowances in the basic salary 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,
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) # Total Amount
sheet.write(row, 3, data_list[2], format1) # Beneficiary Account IBAN
sheet.write(row, 4, data_list[1], format1) # Beneficiary Name
sheet.write(row, 5, data_list[3], format1) # Beneficiary Bank CODE
sheet.write(row, 6, '', format1) # Payment Description (empty)
sheet.write(row, 7, data_list[6], format1) # Total Allowances
sheet.write(row, 8, data_list[7], format1) # Housing (0)
sheet.write(row, 9, data_list[8], format1) # Other (0)
sheet.write(row, 10, data_list[9], format1) # Deductions
sheet.write(row, 11, data_list[5], format1) # Beneficiary National/IqamaID
return row
def _write_overtime_data(self, sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1):
"""Write overtime 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 overtime, 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) # Total Amount
sheet.write(row, 3, data_list[2], format1) # Beneficiary Account IBAN
sheet.write(row, 4, data_list[1], format1) # Beneficiary Name
sheet.write(row, 5, data_list[3], format1) # Beneficiary Bank CODE
sheet.write(row, 6, '', format1) # Payment Description (empty)
sheet.write(row, 7, data_list[6], format1) # Basic (0)
sheet.write(row, 8, data_list[7], format1) # Housing (0)
sheet.write(row, 9, data_list[8], format1) # Overtime in Other Earnings
sheet.write(row, 10, data_list[9], format1) # Deductions
sheet.write(row, 11, data_list[5], format1) # Beneficiary National/IqamaID
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) # Total Amount
sheet.write(row, 3, data_list[2], format1) # Beneficiary Account IBAN
sheet.write(row, 4, data_list[1], format1) # Beneficiary Name
sheet.write(row, 5, data_list[3], format1) # Beneficiary Bank CODE
sheet.write(row, 6, '', format1) # Payment Description (empty)
sheet.write(row, 7, data_list[6], format1) # Basic (0)
sheet.write(row, 8, data_list[7], format1) # Housing (0)
sheet.write(row, 9, data_list[8], format1) # Mission allowance in Other Earnings
sheet.write(row, 10, data_list[9], format1) # Deductions
sheet.write(row, 11, data_list[5], format1) # Beneficiary National/IqamaID
return row
def _write_training_data(self, sheet, row, date_from, date_to, entry_type, employees, salary, salary_ids, bank, branch, company_id, format1):
"""Write training 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 training, 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) # Total Amount
sheet.write(row, 3, data_list[2], format1) # Beneficiary Account IBAN
sheet.write(row, 4, data_list[1], format1) # Beneficiary Name
sheet.write(row, 5, data_list[3], format1) # Beneficiary Bank CODE
sheet.write(row, 6, '', format1) # Payment Description (empty)
sheet.write(row, 7, data_list[6], format1) # Basic (0)
sheet.write(row, 8, data_list[7], format1) # Housing (0)
sheet.write(row, 9, data_list[8], format1) # Training allowance in Other Earnings
sheet.write(row, 10, data_list[9], format1) # Deductions
sheet.write(row, 11, data_list[5], format1) # Beneficiary National/IqamaID
return row
class HrPayslipExtended(models.Model):
"""Extend hr.payslip to support confirmed state filtering"""
_inherit = 'hr.payslip'
@api.model
def search(self, args, offset=0, limit=None, order=None, count=False):
"""Override search to use confirmed state when context flag is set"""
# Check if we should use confirmed state for XLSX reports
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 parent XLSX report to use confirmed state for all bank types"""
_inherit = 'report.exp_payroll_custom.report_payroll_bank_xlsx'
@api.model
def generate_xlsx_report(self, workbook, data, payslips):
"""Override to use confirmed state in XLSX reports only"""
# Call parent with context flag to use confirmed state
return super(PayrollXlsxExtended, self.with_context(use_payslip_run_confirmed_state=True)).generate_xlsx_report(workbook, data, payslips)
class PayrollBankAlbiladReportPdf(models.AbstractModel):
"""Al-Bilad Bank Payroll Report PDF"""
_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):
"""Prepare data for PDF report"""
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 = []
# Process payslips based on no_details flag
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
# Convert dates to strings for display
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 = ''
# Get common fields safely
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':
# For salary, calculate basic, housing, and other
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)

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Al-Bilad Bank XLSX Report -->
<record id="report_payroll_bank_albilad_xlsx" model="ir.actions.report">
<field name="name">Al-Bilad Bank Payroll Report</field>
<field name="model">payroll.bank.wiz</field>
<field name="report_type">xlsx</field>
<field name="report_name">exp_payroll_albilad.payroll_bank_albilad_xlsx</field>
<field name="report_file">exp_payroll_albilad.payroll_bank_albilad_xlsx</field>
<field name="binding_model_id" ref="exp_payroll_custom.model_payroll_bank_wiz"/>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import payroll_bank_report

View File

@ -0,0 +1,101 @@
# -*- 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):
"""Override print_report to use Al-Bilad report when albilad bank is selected"""
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:
# Call parent method for other bank types
return super(BankPayslipReportAlbilad, self).print_report()
def print_pdf_report(self):
"""Print Al-Bilad PDF report when albilad bank is selected"""
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_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:
# If parent doesn't have print_pdf_report, return a message
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': _('PDF report is only available for Al-Bilad bank type.'),
'type': 'warning',
'sticky': False,
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Extend the bank payroll report wizard view for Al-Bilad Bank -->
<record id="bank_payroll_report_form_albilad" model="ir.ui.view">
<field name="name">payroll.bank.wiz.form.albilad</field>
<field name="model">payroll.bank.wiz</field>
<field name="inherit_id" ref="exp_payroll_custom.bank_payroll_report_form"/>
<field name="arch" type="xml">
<!-- Make bank_ids field always visible and required when albilad bank type is selected -->
<field name="bank_ids" position="attributes">
<attribute name="attrs">{'required':[('bank_type','=','albilad')]}</attribute>
</field>
</field>
</record>
</odoo>