Merge branch 'dev_odex25_ensan' into you_dev_odex25_ensan

This commit is contained in:
kchyounes19 2025-10-14 15:26:06 +01:00 committed by GitHub
commit b5c697507d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 2189 additions and 1558 deletions

View File

@ -36,7 +36,7 @@ jobs:
# Ensan Project
ensan_master_server:
name: Deploy to Ensan Master
runs-on: ensan-client-project-runner
runs-on: new-ensan-vpn-client-server-runner
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'Ensan-Project' && (github.ref == 'refs/heads/master_odex-event' || github.ref == 'refs/heads/master_odex25_helpdesk' || github.ref == 'refs/heads/master_odex25_accounting' || github.ref == 'refs/heads/master_odex25_base' || github.ref == 'refs/heads/master_odex25_dms' || github.ref == 'refs/heads/master_odex25_fleet' || github.ref == 'refs/heads/master_odex25_ENSAN' || github.ref == 'refs/heads/master_odex25_hr' || github.ref == 'refs/heads/master_odex25_inventory' || github.ref == 'refs/heads/master_odex25_maintenance' || github.ref == 'refs/heads/master_odex25_mobile' || github.ref == 'refs/heads/master_odex25_pos' || github.ref == 'refs/heads/master_odex25_project' || github.ref == 'refs/heads/master_odex25_purchase' || github.ref == 'refs/heads/master_odex25_realstate' || github.ref == 'refs/heads/master_odex25_sales' || github.ref == 'refs/heads/master_odex25_survey' || github.ref == 'refs/heads/master_odex25_transactions' || github.ref == 'refs/heads/master_odex25_website' || github.ref == 'refs/heads/master_openeducat_erp-14.0.1.0' || github.ref == 'refs/heads/master_odex25_ensan') &&
(github.actor == 'moutazmuhammad' ||
github.actor == 'expsa' ||

View File

@ -9,7 +9,7 @@ class ServicesSettings(models.Model):
help='Link the service to an HR department'
)
hr_department_id = fields.Many2one(
'hr.department', string='Linked Department', readonly=True
'hr.department', string='Linked Department'
)
manager_id = fields.Many2one(
'hr.employee', string='Manager',
@ -29,59 +29,6 @@ class ServicesSettings(models.Model):
])
@api.model
def create(self, vals):
service = super().create(vals)
if service.linked_to_department:
dept = self.env['hr.department'].search([('name', '=', service.service_name)], limit=1)
if not dept:
dept = self.env['hr.department'].create({
'name': service.service_name,
'department_type': 'unit',
'manager_id': service.manager_id.id,
'service_link_id': service.id
})
service.hr_department_id = dept.id
return service
def write(self, vals):
res = super().write(vals)
for service in self:
# If linked_to_department field updated
if 'linked_to_department' in vals:
if service.linked_to_department and not service.hr_department_id:
# Create department if missing
dept = self.env['hr.department'].search([('name', '=', service.service_name)], limit=1)
if not dept:
dept = self.env['hr.department'].create({
'name': service.service_name,
'department_type': 'unit',
'manager_id': service.manager_id.id,
'service_link_id': service.id
})
service.hr_department_id = dept.id
elif not service.linked_to_department and service.hr_department_id:
# Remove linked department if no dependencies (employees or requests)
hr_dept = service.hr_department_id
empowerment_request = self.env['empowerment.request'].search([('department_id', '=', hr_dept.id)], limit=1)
if hr_dept.employee_ids or empowerment_request:
raise ValidationError(_('لا يمكن فك الربط، قسم الموارد البشرية مرتبط بموظفين أو طلبات.'))
hr_dept.unlink()
service.hr_department_id = False
return res
def unlink(self):
for service in self:
if service.linked_to_department and service.hr_department_id:
hr_dept = service.hr_department_id
empowerment_request = self.env['empowerment.request'].search([('department_id', '=', hr_dept.id)], limit=1)
if hr_dept.employee_ids or empowerment_request:
raise ValidationError(_('لا يمكن حذف الخدمة، القسم مرتبط بموظفين أو طلبات.'))
hr_dept.unlink()
return super().unlink()
class HrDepartment(models.Model):
_inherit = 'hr.department'

View File

@ -9,7 +9,7 @@
<group>
<field name="linked_to_department" />
<field name="hr_department_id" readonly="1" />
<field name="hr_department_id"/>
<field name="manager_id" />
</group>

View File

@ -124,8 +124,11 @@ class CreatePortalAccount(http.Controller):
# Create the grant.benefit record and let its create flow run
grant_benefit = request.env['grant.benefit'].sudo().with_context(force_website=True, is_benefit=True).create(benefit_data)
else:
grant_benefit = None
elif account_type == 'donor':
request.env['takaful.sponsor'].sudo().create({
'partner_id': partner.id,
'user_id': user.id,
})
# Return success response
data = {

View File

@ -9854,6 +9854,7 @@ msgstr "احتساب قيمة الإيجار"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__rent_attachment
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__member_rent_attachment
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_rent_attachment
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__rent_attachment
msgid "Rent Attachment"
msgstr "مرفق عقد الإيجار"
@ -9865,6 +9866,7 @@ msgstr "معلومات عقد الإيجار"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__member_rent_contract_number
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_rent_contract_number
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__rent_contract_number
msgid "Rent Contract Number"
msgstr "رقم العقد"
@ -9872,6 +9874,7 @@ msgstr "رقم العقد"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__rent_end_date
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__member_rent_end_date
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_rent_end_date
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__rent_end_date
msgid "Rent End Date"
msgstr "تاريخ نهاية العقد"
@ -9905,6 +9908,7 @@ msgstr "سكن مشترك إيجار"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__rent_start_date
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__member_rent_start_date
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_rent_start_date
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__rent_start_date
msgid "Rent Start Date"
msgstr "تاريخ بداية العقد"
@ -10072,6 +10076,7 @@ msgstr ""
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__required_attach
#: model:ir.model.fields,field_description:odex_benefit.field_services_settings__required_attach
msgid "Required Attach"
msgstr "ملفات مطلوبة"
@ -10114,7 +10119,7 @@ msgstr "الاخصائي الاجتماعي"
#. module: odex_benefit
#: model_terms:ir.ui.view,arch_db:odex_benefit.view_confirm_benefit_expense_form
msgid "Reset"
msgstr ""
msgstr "إرجاع"
#. module: odex_benefit
#: code:addons/odex_benefit/models/benefit.py:0
@ -10257,7 +10262,7 @@ msgstr ""
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__sa_iban
msgid "SA"
msgstr ""
msgstr "الايبان"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_benefits_representative__is_seo_optimized
@ -15943,6 +15948,30 @@ msgstr "سنوات"
msgid "Max Amount"
msgstr "الحد الأقصى للمبلغ"
#. module: odex_benefit
#: model:ir.model.fields.selection,name:odex_benefit.selection__confirm_benefit_expense__state__assistant_general_manager
msgid "Waiting For The Assistant General Manager"
msgstr "بإنتظار مساعد المدير العام"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_confirm_benefit_expense__start_date
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_start
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__start
msgid "Start Date"
msgstr "تاريخ البداية"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_confirm_benefit_expense__end_date
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__end
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_end
msgid "End Date"
msgstr "تاريخ النهاية"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__last_disbursement_date
msgid "Last disbursement date"
msgstr "تاريخ أخر صرف"
#. module: odex_benefit
#: model_terms:ir.ui.view,arch_db:odex_benefit.services_settings_form
msgid "Amounts by Categories"
@ -16366,4 +16395,40 @@ msgstr "موافقة الإدارة القانونية"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_services_settings__needs_project_management_approval
msgid "Needs Project Management Approval"
msgstr "موافقة إدارة المشاريع"
msgstr "موافقة إدارة المشاريع"
#. module: odex_benefit
#: model_terms:ir.ui.view,arch_db:odex_benefit.services_settings_form
msgid "Services Attachments"
msgstr "المستندات المطلوبة"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_rent_payment_date_exception
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__rent_payment_date_exception
msgid "Rent Payment Date Exception?"
msgstr "استثناء في تاريخ دفع الإيجار؟"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__rent_amount_payment
msgid "Rent Amount Payment"
msgstr "مبلغ دفع الإيجار"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_rent_contract
msgid "New Rent Contract?"
msgstr "عقد إيجار جديد؟"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__estimated_rent_amount_payment
msgid "Estimated Rent Amount Payment"
msgstr "المبلغ التقديري لدفع الإيجار"
#. module: odex_benefit
#: model_terms:ir.ui.view,arch_db:odex_benefit.service_request_form
msgid "New Rent Contract Information"
msgstr "معلومات عقد الإيجار الجديد"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__new_rent_amount_payment
msgid "New Rent Amount Payment"
msgstr "مبلغ دفع الإيجار الجديد"

View File

@ -609,6 +609,7 @@ class GrantBenefitProfile(models.Model):
related='company_id.currency_id')
family_edit = fields.Boolean(string='Family Edit', default=False)
family_return_reason = fields.Text(string="Family Return Reason")
last_disbursement_date = fields.Date(string="Last disbursement date", readonly=True)
_sql_constraints = [
('unique_code', "unique (code) WHERE state NOT IN ('draft', 'new')", 'This code already exists')

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from dateutil.relativedelta import relativedelta
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.tools import date_utils
class ConfirmBenefitExpense(models.Model):
@ -10,9 +10,24 @@ class ConfirmBenefitExpense(models.Model):
_description = 'Confirm Benefit Expense'
_inherit = ['mail.thread', 'mail.activity.mixin']
# region [Default Methods]
def _default_start_date(self):
today = fields.Date.today()
start_date = date_utils.start_of(today, 'month')
return start_date
def _default_end_date(self):
today = fields.Date.today()
end_date = date_utils.end_of(today, 'month')
return end_date
# endregion [Default Methods]
family_expense_seq = fields.Char(string="Family Expense Sequence", copy=False, readonly=True, default=lambda x: _('New'))
state = fields.Selection(selection=[
('draft', 'Draft'),
('assistant_general_manager', 'Waiting For The Assistant General Manager'), # Translate
('depart_manager', 'Department Manager'),
('account_manager', 'Account Manager'),
('cancel', 'Cancelled'),
@ -25,8 +40,10 @@ class ConfirmBenefitExpense(models.Model):
journal_id = fields.Many2one(comodel_name='account.journal', string="Journal", required=True,copy=False)
name = fields.Char(string="Name", states={'confirm': [('readonly', True)]}, copy=False)
date = fields.Date(string="Date", default=fields.Date.context_today, required=True,
date = fields.Date(string="Date", default=fields.Date.context_today, required=False,
states={'confirm': [('readonly', True)]})
start_date = fields.Date(string="Start Date", default=_default_start_date, required=True)
end_date = fields.Date(string="End Date", required=True, default=_default_end_date)
family_ids = fields.Many2many(comodel_name='grant.benefit', relation='benefit_expense_grant_rel',
column1='expense_id',
@ -82,7 +99,7 @@ class ConfirmBenefitExpense(models.Model):
# else:
# record.family_ids = [(5,)] # Clear the records if source_field is empty
@api.depends('expense_type', 'date', 'branch_custom_id')
@api.depends('expense_type', 'date', 'branch_custom_id', 'start_date', 'end_date')
def _compute_domain_ids(self):
for rec in self:
journal_domain = []
@ -105,14 +122,15 @@ class ConfirmBenefitExpense(models.Model):
if rec.expense_type == 'family_invoice':
base_domain.append(('meal_card', '=', True))
if rec.date:
# if rec.date:
if rec.start_date and rec.end_date:
# Calculate the start date for the past month range
month_ago = rec.date - relativedelta(months=1)
# month_ago = rec.date - relativedelta(months=1)
# Search for conflicting records of the same expense type within the past month
conflicting_records = self.search([
('date', '>=', month_ago),
('date', '<=', rec.date),
('date', '>=', rec.start_date),
('date', '<=', rec.end_date),
('expense_type', '=', rec.expense_type),
])
@ -152,6 +170,28 @@ class ConfirmBenefitExpense(models.Model):
else:
self.journal_id = False
def action_assistant_manager(self):
if self.expense_type != 'family_expense':
self.state = 'assistant_general_manager'
return
disbursement_date = False
# if not disbursement_date:
last_expense = self.search(
[("state", "=", "confirm"), ("end_date", "!=", False)],
order="id desc",
limit=1
)
if last_expense:
disbursement_date = last_expense.end_date
# Apply the disbursement date to all families
if disbursement_date:
for family in self.family_ids:
family.last_disbursement_date = disbursement_date
self.state = 'assistant_general_manager'
def action_depart_manager(self):
self.state = 'depart_manager'
@ -246,7 +286,7 @@ class ConfirmBenefitExpense(models.Model):
# for pay in self:
# pay.available_payment_method_line_ids = pay.journal_id._get_available_payment_method_lines('outbound')
@api.onchange('expense_type', 'date', 'branch_custom_id')
@api.onchange('expense_type', 'date', 'branch_custom_id', 'start_date', 'end_date')
def _onchange_expense_type(self):
"""Restrict families to a single expense type per month."""
journal_domain = []
@ -270,14 +310,15 @@ class ConfirmBenefitExpense(models.Model):
if self.expense_type == 'family_invoice':
base_domain.append(('meal_card', '=', True))
if self.date:
# if self.date:
if self.start_date and self.end_date:
# Calculate the start date for the past month range
month_ago = self.date - relativedelta(months=1)
# Search for conflicting records of the same expense type within the past month
conflicting_records = self.search([
('date', '>=', month_ago),
('date', '<=', self.date),
('date', '>=', self.start_date),
('date', '<=', self.end_date),
('expense_type', '=', self.expense_type),
])

View File

@ -18,8 +18,8 @@ class ServiceRequest(models.Model):
member_id = fields.Many2one('family.member',domain="[('benefit_id','=',family_id)]",string='Member')
description = fields.Char(string='Description')
need_status = fields.Selection(string='Need Status',selection=[('urgent', 'urgent'),('not_urgent', 'Not urgent')])
main_service_category = fields.Many2one('services.settings',domain="[('is_main_service','=',True)]",string="Main Service Category")
sub_service_category = fields.Many2one('services.settings',domain="[('is_main_service','=',False),('service_type','=',False),('parent_service','=',main_service_category)]",string='Sub Service Category')
main_service_category = fields.Many2one('services.settings',domain="[('service_type','=','main_service')]",string="Main Service Category")
sub_service_category = fields.Many2one('services.settings',domain="[('service_type','=','main_service'),('service_type','=',False),('parent_service','=',main_service_category)]",string='Sub Service Category')
service_cat = fields.Many2one('services.settings',string='Service Cat.')
available_service_cats = fields.Many2many('services.settings', compute='_compute_available_service_cats', store=True)
service_attach = fields.Many2many('ir.attachment', 'rel_service_attachment_service_request', 'service_request_id','attachment_id', string='Service Attachment')
@ -170,6 +170,28 @@ class ServiceRequest(models.Model):
service_max_amount = fields.Float(string="Maximum Amount", copy=False)
rent_period = fields.Integer('Rent Period')
is_orphan = fields.Boolean(string='Orphaned (Both Parents Deceased)',compute='_compute_is_orphan',store=True)
has_money_for_payment_is_appearance = fields.Boolean(string='Has money Field is appearance?',compute='_get_money_for_payment_is_appearance')
has_money_for_payment = fields.Selection([('yes', 'Yes'), ('no', 'No')], string='Has money for payment?')
has_money_to_pay_first_payment = fields.Selection([('yes', 'Yes'), ('no', 'No')],
string='Has money to pay first payment?')
has_money_field_is_appearance = fields.Boolean(string='Has money Field is appearance?',
compute='_get_money_field_is_appearance')
@api.depends('requested_service_amount', 'service_max_amount')
def _get_money_for_payment_is_appearance(self):
for rec in self:
if rec.requested_service_amount and rec.service_max_amount and rec.requested_service_amount > rec.service_max_amount:
rec.has_money_for_payment_is_appearance = True
else:
rec.has_money_for_payment_is_appearance = False
@api.depends('requested_service_amount', 'service_max_amount')
def _get_money_field_is_appearance(self):
for rec in self:
if rec.requested_service_amount and rec.service_max_amount and rec.requested_service_amount > rec.service_max_amount:
rec.has_money_field_is_appearance = True
else:
rec.has_money_field_is_appearance = False
@api.depends('family_id.mother_marital_conf','family_id.replacement_mother_marital_conf')
def _compute_is_orphan(self):
@ -492,16 +514,21 @@ class ServiceRequest(models.Model):
@api.onchange('member_id','family_id','eid_gift_benefit_count','service_cat')
def _onchange_member(self):
for rec in self:
if rec.benefit_type == 'family' and rec.service_type == 'eid_gift':
rec.requested_service_amount = rec.eid_gift_benefit_count * rec.service_cat.eid_gift_member_amount
if rec.benefit_type == 'member' and rec.service_type == 'eid_gift':
rec.requested_service_amount = rec.service_cat.eid_gift_member_amount
if rec.benefit_type == 'family' and rec.service_type == 'winter_clothing':
rec.requested_service_amount = rec.benefit_member_count * rec.service_cat.winter_clothing_member_amount
if rec.benefit_type == 'member' and rec.service_type == 'winter_clothing':
rec.requested_service_amount = rec.service_cat.winter_clothing_member_amount
if rec.benefit_type == 'family' and rec.service_type == 'ramadan_basket':
rec.requested_service_amount = rec.service_cat.ramadan_basket_member_amount
if rec.family_id:
if rec.benefit_type == 'family' and rec.service_type == 'eid_gift':
rec.requested_service_amount = rec.eid_gift_benefit_count * rec.service_cat.eid_gift_member_amount
if rec.benefit_type == 'member' and rec.service_type == 'eid_gift':
rec.requested_service_amount = rec.service_cat.eid_gift_member_amount
if rec.benefit_type == 'family' and rec.service_type == 'winter_clothing':
rec.requested_service_amount = rec.benefit_member_count * rec.service_cat.winter_clothing_member_amount
if rec.benefit_type == 'member' and rec.service_type == 'winter_clothing':
rec.requested_service_amount = rec.service_cat.winter_clothing_member_amount
if rec.benefit_type == 'family' and rec.service_type == 'ramadan_basket':
rec.requested_service_amount = rec.service_cat.ramadan_basket_member_amount
else:
rec.member_id = False
rec.service_cat = False
rec.available_service_cats = False
@api.onchange('service_cat','family_id')
@ -538,7 +565,7 @@ class ServiceRequest(models.Model):
interval = rec.service_cat.recurrence_interval or 1
period = rec.service_cat.recurrence_period or 'months'
max_limit_type = rec.service_cat.max_limit_type
special_services = ['home_furnishing', 'electrical_devices']
special_services = ['home_furnishing', 'electrical_devices','rent','alternative_housing']
if rec.service_cat.service_type == 'buy_car':
if rec.family_id.has_car:
raise ValidationError(_("You cannot request this service because you have a car."))
@ -841,7 +868,7 @@ class ServiceRequest(models.Model):
ids.append(rec.id)
default_service_ids = ids
service_requests = self.env['service.request'].browse(ids)
if any(request.state not in 'accounting_approve' for request in service_requests):
if any(request.state not in 'send_request_to_supplier' for request in service_requests):
raise UserError(_("All selected requests should be in Accounting Approve state"))
if any(request.payment_order_id for request in service_requests):
raise UserError(_("All selected requests should be not has payment order"))
@ -861,7 +888,7 @@ class ServiceRequest(models.Model):
ids.append(rec.id)
service_requests = self.env['service.request'].browse(ids)
service_producer_id = self.env['service.request'].search([('id','=',ids[0])],limit=1)
if any(request.state not in 'family_received_device' for request in service_requests):
if any(request.state not in 'approval_of_beneficiary_services' for request in service_requests):
raise UserError(_("All selected requests should be in Family Received Device state"))
if any(request.vendor_bill for request in service_requests):
raise UserError(_("All selected requests should be not has Vendor Bill"))

View File

@ -1431,6 +1431,7 @@
<field name="family_monthly_meals"/>
<field name="family_monthly_clotting"/>
<field name="total_family_expenses"/>
<field name="last_disbursement_date"/>
</group>
</page>
<page string="Family Cars">

View File

@ -23,130 +23,135 @@
<form string="Confirm Benefit Expense">
<header>
<field name="state" widget="statusbar"
statusbar_visible="draft,depart_manager,account_manager,confirm"/>
<button string="Cancel" type="object" name="action_cancel" class="oe_highlight"
states="depart_manager,account_manager"/>
<button string="Reset" type="object" name="action_reset_to_draft" class="oe_highlight"
states="cancel"/>
statusbar_visible="draft,assistant_general_manager,depart_manager,account_manager,confirm"/>
<!-- <button string="Cancel" type="object" name="action_cancel" class="btn btn-danger"-->
<!-- states="assistant_general_manager,depart_manager,account_manager"-->
<!-- groups="odex_benefit.group_benefit_manager"/>-->
<button string="Confirm" type="object" name="action_assistant_manager" class="oe_highlight"
states="draft"/>
<button string="Confirm" type="object" name="action_depart_manager" class="oe_highlight"
states="draft" groups="odex25_account_payment_fix.group_depart_manager"/>
states="assistant_general_manager" groups="odex25_account_payment_fix.group_depart_manager"/>
<button string="Reset" type="object" name="action_reset_to_draft" class="btn btn-danger"
states="assistant_general_manager" groups="odex25_account_payment_fix.group_depart_manager"/>
<button string="Confirm" type="object" name="action_accounting_manager" class="oe_highlight"
states="depart_manager" groups="odex25_account_payment_fix.group_accounting_manager"/>
<button string="Reset" type="object" name="action_reset_to_draft" class="btn btn-danger"
states="depart_manager" groups="odex25_account_payment_fix.group_accounting_manager"/>
<button string="Confirm" type="object" name="action_confirm_selected" class="oe_highlight"
states="account_manager" groups="odex25_account_payment_fix.group_general_manager"/>
<button string="Reset" type="object" name="action_reset_to_draft" class="btn btn-danger"
states="account_manager" groups="odex25_account_payment_fix.group_general_manager"/>
</header>
<sheet>
<group invisible="1">
<field name="family_domain_ids" widget="many2many_tags" />
<field name="journal_domain_ids" widget="many2many_tags"/>
</group>
<div class="oe_button_box" name="button_box">
<button icon="fa-usd" name="action_open_related_move_records" type="object"
attrs="{'invisible':[('expense_type','!=','family_expense')]}">
<field name="total_moves" string="Moves" widget="statinfo"/>
</button>
<button icon="fa-usd" name="action_open_related_move_line_records" type="object"
attrs="{'invisible':[('expense_type','!=','family_expense')]}">
<field name="total_move_lines" string="Move Lines" widget="statinfo"/>
</button>
<button icon="fa-usd" name="action_open_related_invoice_records" type="object"
attrs="{'invisible':[('expense_type','!=','family_invoice')]}">
<field name="total_invoices" string="Invoices" widget="statinfo"/>
</button>
<field name="family_domain_ids" widget="many2many_tags" invisible="1"/>
<field name="journal_domain_ids" widget="many2many_tags" invisible="1"/>
<div class="oe_title">
<h1>
<field name="family_expense_seq" readonly="1"/>
</h1>
</div>
<group>
<div class="oe_title">
<h1>
<field name="family_expense_seq" readonly="1"/>
</h1>
</div>
</group>
<group>
<field name="expense_type" required="1"/>
<field name="journal_id" required="1" domain="[('id', 'in', journal_domain_ids)]" attrs="{'invisible': [('expense_type', '!=', 'family_invoice')],'readonly':[('state', '=', 'confirm')]}" forec_save="1"/>
<!-- <field name="payment_method_id" attrs="{'invisible': [('expense_type', '=', 'family_invoice')],'required': [('expense_type', '=', 'family_expense')]}"/>-->
</group>
<group>
<field name="name" required="1" string="Process Details"/>
<field name="date" required="1"/>
<field name="branch_custom_id" required="1"/>
<group>
<field name="name" required="1" string="Process Details"
attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<label for="start_date"/>
<div>
<field name="start_date"
attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<strong><label for="end_date"/></strong>
<field name="end_date"
attrs="{'readonly':[('state', '!=', 'draft')]}"/>
</div>
<field name="branch_custom_id" required="1"
attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<!-- <field name="payment_method_id" attrs="{'invisible': [('expense_type', '=', 'family_invoice')],'required': [('expense_type', '=', 'family_expense')]}"/>-->
</group>
<group>
<field name="expense_type" required="1"/>
<field name="journal_id" required="1" domain="[('id', 'in', journal_domain_ids)]"
attrs="{'invisible': [('expense_type', '!=', 'family_invoice')]}" forec_save="1"/>
<field name="date" required="1" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
</group>
</group>
<!-- Conditional fields based on expense_type -->
<group>
<group>
<field name="available_payment_method_line_ids" invisible="1"/>
<field name="cash_expense"
attrs="{'invisible': [('expense_type', '=', 'family_invoice')]}"/>
<field name="meal_expense"
attrs="{'invisible': [('expense_type', '=', 'family_invoice')]}"/>
<field name="cloth_expense"
attrs="{'invisible': [('expense_type', '=', 'family_invoice')]}"/>
</group>
<group>
<field name="family_monthly_income"
attrs="{'invisible': ['|',('expense_type', '=', 'family_invoice'),('cash_expense', '=', False)]}"/>
<field name="family_monthly_meals" attrs="{'invisible': [('expense_type', '=', 'family_expense'),('meal_expense', '=', False)]}"/>
<field name="family_monthly_clotting" attrs="{'invisible': ['|',('expense_type', '=', 'family_invoice'),('cloth_expense', '=', False)]}"/>
</group>
<field name="available_payment_method_line_ids" invisible="1"/>
<field name="cash_expense"
attrs="{'invisible': [('expense_type', '=', 'family_invoice')], 'readonly':[('state', '!=', 'draft')]}"/>
<field name="meal_expense"
attrs="{'invisible': [('expense_type', '=', 'family_invoice')], 'readonly':[('state', '!=', 'draft')]}"/>
<field name="cloth_expense"
attrs="{'invisible': [('expense_type', '=', 'family_invoice')], 'readonly':[('state', '!=', 'draft')]}"/>
</group>
<group>
<field name="family_monthly_income"
attrs="{'invisible': ['|',('expense_type', '=', 'family_invoice'),('cash_expense', '=', False)]}"/>
<field name="family_monthly_meals"
attrs="{'invisible': [('expense_type', '=', 'family_expense'),('meal_expense', '=', False)]}"/>
<field name="family_monthly_clotting"
attrs="{'invisible': ['|',('expense_type', '=', 'family_invoice'),('cloth_expense', '=', False)]}"/>
</group>
</group>
<!-- Notebook with Families page -->
<notebook>
<page string="Families">
<field name="family_ids" domain="[('id', 'in', family_domain_ids)]">
<tree string="Family">
<field name="name"/>
<field name="code"/>
<field name="name"/>
<field name="sms_phone" optional="hide"/>
<field name="researcher_id" optional="hide"/>
<field name="sa_iban" />
<!-- <field name="father_id_number" />-->
<field name="meal_card"/>
<field name="family_monthly_income"/>
<field name="family_monthly_meals"/>
<field name="family_monthly_clotting"/>
<field name="total_family_expenses"/>
<field name="father_id_number"/>
<field name="sms_phone"/>
<field name="benefit_member_count"/>
<field name="non_member_count"/>
<field name="researcher_id"/>
<field name="last_visit_date"/>
<field name="state"/>
<field name="non_member_count" optional="hide"/>
<field name="family_monthly_income" attrs="{'column_invisible': [('parent.expense_type', '!=', 'family_expense')]}"/>
<field name="family_monthly_meals"/>
<field name="family_monthly_clotting" attrs="{'column_invisible': [('parent.expense_type', '!=', 'family_expense')]}"/>
<field name="total_family_expenses" attrs="{'column_invisible': [('parent.expense_type', '!=', 'family_expense')]}"/>
<field name="last_disbursement_date" optional="hide"/>
<!-- <field name="last_visit_date" optional="hide"/>-->
</tree>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="activity_ids" />
<field name="message_ids" widget="mail_thread"/>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="activity_ids"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_confirm_benefit_expense_search">
<field name="name">confirm.benefit.expense</field>
<field name="model">confirm.benefit.expense</field>
<field name="arch" type="xml">
<search>
<group string="Group By">
<field name="name">confirm.benefit.expense</field>
<field name="model">confirm.benefit.expense</field>
<field name="arch" type="xml">
<search>
<group string="Group By">
<filter string="Branch" name="branch_custom_id" context="{'group_by': 'branch_custom_id'}"/>
</group>
<separator/>
<filter string="Draft" name="draft" domain="[('state','=','draft')]"/>
<separator/>
<separator/>
<filter string="Depart Manager" name="depart_manager" domain="[('state', '=', 'depart_manager')]"/>
<separator/>
<separator/>
<filter string="Account Manager" name="account_manager" domain="[('state', '=', 'account_manager')]"/>
<separator/>
<separator/>
<filter string="Cancel" name="cancel" domain="[('state', '=', 'cancel')]"/>
<separator/>
<separator/>
<filter string="Confirm" name="confirm" domain="[('state', '=', 'confirm')]"/>
<separator/>
</search>
</field>
</group>
<separator/>
<filter string="Draft" name="draft" domain="[('state','=','draft')]"/>
<separator/>
<separator/>
<filter string="Depart Manager" name="depart_manager" domain="[('state', '=', 'depart_manager')]"/>
<separator/>
<separator/>
<filter string="Account Manager" name="account_manager" domain="[('state', '=', 'account_manager')]"/>
<separator/>
<separator/>
<filter string="Cancel" name="cancel" domain="[('state', '=', 'cancel')]"/>
<separator/>
<separator/>
<filter string="Confirm" name="confirm" domain="[('state', '=', 'confirm')]"/>
<separator/>
</search>
</field>
</record>
<!-- Action -->

View File

@ -143,6 +143,7 @@
<field name="education_status"/>
<field name="benefit_id" attrs="{'readonly': [('state','!=','draft')]}" force_save="1"
required="1"/>
<field name="partner_id" />
</group>
<group>
<field name="is_work"

View File

@ -18,18 +18,19 @@
<field name="benefit_type" required="1"/>
<field name="allow_non_beneficiary" widget="boolean_toggle"/>
<field name="service_producer_id"
attrs="{'invisible':['|',('is_main_service','=',True),('is_service_producer','=',False)]}"/>
attrs="{'invisible':['|',('service_type','=','main_service'),('is_service_producer','=',False)]}"/>
</group>
<group>
<field name="service_category"/>
<field name="parent_service" attrs="{'invisible':[('is_main_service','=',True)]}"/>
<field name="parent_service"
attrs="{'invisible':[('service_type','=','main_service')]}"/>
<field name="benefit_category_ids" widget="many2many_tags" required="1"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<notebook>
<page string="Settings">
<page string="Settings" attrs="{'invisible':[('service_type','=','main_service')]}">
<group>
<group>
<field name="max_limit_type"/>
@ -72,21 +73,24 @@
</group>
<group>
<group>
<field name="is_main_service" widget="boolean_toggle"/>
<field name="is_main_service" invisible="1" widget="boolean_toggle"/>
<field name="is_service_producer" widget="boolean_toggle"
attrs="{'invisible':[('is_main_service','=',True)]}"/>
<field name="is_this_service_for_student" widget="boolean_toggle"
attrs="{'invisible':[('is_main_service','=',True)]}"/>
attrs="{'invisible':[('service_type','=','main_service')]}"/>
<field name="is_this_service_for_student" invisible="1"
widget="boolean_toggle"/>
</group>
<group>
<field name="required_attach" widget="boolean_toggle"
attrs="{'invisible':[('is_main_service','=',True)]}"/>
attrs="{'invisible':[('service_type','=','main_service')]}"/>
<field name="is_seasonal_service" widget="boolean_toggle"/>
<field name="show_in_portal" widget="boolean_toggle"/>
</group>
</group>
</page>
<page string="Accounts" attrs="{'invisible':[('is_main_service','=',True)]}">
<page string="Accounts" attrs="{'invisible':[('service_type','=','main_service')]}">
<group>
<group>
<field name="account_id"
@ -97,7 +101,7 @@
</group>
</group>
</page>
<page string="Eligibility Criteria">
<page string="Eligibility Criteria" attrs="{'invisible':[('service_type','=','main_service')]}">
<group>
<field name="service_conditions"/>
</group>
@ -252,8 +256,9 @@
<field name="name">services.settings.tree</field>
<field name="model">services.settings</field>
<field name="arch" type="xml">
<tree string="Services Settings">
<tree string="Services Settings" decoration-info="service_type=='main_service'">
<field name="service_number"/>
<field name="service_type" invisible="1"/>
<field name="service_name"/>
<field name="service_category"/>
<field name="parent_service"/>

View File

@ -6,7 +6,7 @@
<field name="model">services.settings</field>
<field name="inherit_id" ref="odex_benefit.services_settings_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='is_this_service_for_student']" position="after">
<xpath expr="//field[@name='is_service_producer']" position="after">
<field name="project_create" attrs="{'invisible':[('service_type','=','main_service')]}" widget="boolean_toggle"/>
<field name="category_id" attrs="{'invisible': ['|',('service_type','=','main_service'),('project_create', '=', False)]}"/>
</xpath>

View File

@ -57,9 +57,11 @@
'wizards/refund_payment_wizard.xml',
'wizards/add_details_wizard.xml',
'views/donations_details_lines.xml',
'views/sponsorship_scheduling_line.xml',
'views/takaful_menus_actions.xml',
'views/preferred_communication.xml',
'views/takaful_payment_method.xml',
'views/product_views.xml',
'data/message_template_data.xml',
'wizards/transfer_deduction_wizard_views.xml',

File diff suppressed because it is too large Load Diff

View File

@ -23,4 +23,5 @@ from . import res_partner
from . import preferred_communication
from . import takaful_payment_method
from . import res_config_settings
from . import product
from . import product
from . import donation_details_lines

View File

@ -1,7 +1,6 @@
from odoo import models, fields,api,_
from odoo.exceptions import UserError
class AccountMove(models.Model):
_inherit = 'account.move'
@ -9,8 +8,23 @@ class AccountMove(models.Model):
is_refund_sponsorship = fields.Boolean(string='Is Refund Sponsorship',default=False)
sponsorship_scheduling_line = fields.Many2one('sponsorship.scheduling.line')
payment_details_line = fields.Many2one('payment.details.lines')
sponsorship_id = fields.Many2one('takaful.sponsorship',string='Sponsorship', readonly=True,)
payment_id = fields.Many2one('account.payment',string='Payment', copy=False)
def action_move_line_create(self):
'''
Confirm the vouchers given in ids and create the journal entries for each of them
'''
res = super(AccountMove, self).action_move_line_create()
if res :
self.write({'payment_id':self.move_id.line_ids.mapped('payment_id').id})
@api.onchange('payment_journal_id')
def on_change_payment_journal_id(self):
for rec in self:
rec.account_id = rec.payment_journal_id.default_debit_account_id
def action_register_payment(self):
''' Override the payment registration behavior. '''
# Perform any custom logic before calling the original method
@ -104,6 +118,145 @@ class AccountRegisterPayment(models.TransientModel):
takaful_sponsorship_id = fields.Many2one('takaful.sponsorship')
is_refund_sponsorship = fields.Boolean(string='Is Refund Sponsorship')
def _check_already_sponsored_beneficiaries(self, sponsorship_id):
"""
Check if any beneficiaries in the sponsorship lines are already sponsored
Returns: recordset of conflicting donation lines
"""
sponsorship_id.ensure_one()
# Get all donation lines from the sponsorship
all_lines = sponsorship_id.donations_details_lines_mechanism_ids
conflicting_lines = self.env['donations.details.lines']
for line in all_lines:
# Skip lines without beneficiaries or non-sponsorship lines
if line.donation_type != 'sponsorship':
continue
# Check for individual beneficiary (person sponsorship)
if line.benefit_id:
if self._is_beneficiary_already_sponsored(line.benefit_id, line):
conflicting_lines |= line
continue
# Check for group beneficiaries
if line.benefit_ids:
for benefit in line.benefit_ids:
if self._is_beneficiary_already_sponsored(benefit, line):
conflicting_lines |= line
break # No need to check other beneficiaries in this line
return conflicting_lines
def _is_beneficiary_already_sponsored(self, beneficiary, current_line):
"""
Check if a specific beneficiary is already sponsored in active state
Args:
beneficiary: family.member record
current_line: donations.details.lines record (to exclude from search)
Returns: True if already sponsored, False otherwise
"""
if not beneficiary:
return False
# Search for other active sponsorships with this beneficiary
other_lines = self.env['donations.details.lines'].sudo().search([
'|', ('sponsorship_id', '!=', False), ('sponsorship_mechanism_id', '!=', False),
'|', ('benefit_id', '=', beneficiary.id), ('benefit_ids', 'in', [beneficiary.id]),
('donation_type', '=', 'sponsorship'),
('state', '=', 'active'), # Only check active sponsorships
('id', '!=', current_line.id) # Exclude current line
])
# Check if any of these lines belong to paid sponsorships
for line in other_lines:
sponsorship = line.sponsorship_id or line.sponsorship_mechanism_id
if sponsorship and sponsorship.state == 'paid':
return True
return False
def _create_payments(self):
sponsorship_line_ids = self.env.context.get('sponsorship_line_ids')
sponsorship_lines = []
if sponsorship_line_ids:
sponsorship_lines = self.env['donations.details.lines'].browse(sponsorship_line_ids)
for line in sponsorship_lines:
if line.record_type == 'sponsorship':
for benefit in (line.benefit_id + line.benefit_ids):
if self._is_beneficiary_already_sponsored(benefit, line):
raise UserError(_("Cannot proceed with payment!\n\n"
"The following sponsorship lines contain beneficiaries that are already sponsored by other sponsors:\n"
"Lines: %s\n\n"
"Please choose different beneficiaries for these lines before proceeding with the payment."
) % line.sequence_no)
payments = super(AccountRegisterPayment, self)._create_payments()
if sponsorship_lines:
sponsorship = sponsorship_lines.sponsorship_id or sponsorship_lines.sponsorship_mechanism_id
all_lines = sponsorship.donations_details_lines_mechanism_ids + sponsorship.donations_details_lines
if not self.env.context.get('schedule_line_payment'):
sponsorship_lines.write({'is_paid': True})
sponsorship_lines.filtered(lambda l: l.record_type == 'sponsorship' and (l.benefit_id + l.benefit_ids)).write({'state': 'active'})
sponsorship_lines.filtered(lambda l: l.record_type == 'sponsorship' and not (l.benefit_id + l.benefit_ids)).write({'state': 'waiting'})
sponsorship_lines.filtered(lambda l: l.record_type == 'donation').write({'state': 'paid'})
for line in sponsorship_lines:
if line.record_type != 'sponsorship':
continue
if line.sponsorship_type == 'group':
line.benefit_ids.write({'sponsor_related_id': line.sponsorship_mechanism_id.sponsor_id.id, 'sponsorship_id': line.sponsorship_mechanism_id.id})
if line.sponsorship_type == 'person':
line.benefit_id.write({'sponsor_related_id': line.sponsorship_mechanism_id.sponsor_id.id, 'sponsorship_id': line.sponsorship_mechanism_id.id})
line.sponsorship_mechanism_id.sponsor_id.sudo().is_sponsor_portal = True
if 'draft' in all_lines.mapped('state'):
sponsorship.state = 'wait_pay'
else:
sponsorship.state = 'paid'
payments.write({'takaful_sponsorship_id': sponsorship.id})
vendor_bill_vals = []
benefit_journal_id = self.env.company.kafala_benefit_journal_id.id
for line in sponsorship_lines:
for benefit in (line.benefit_id + line.benefit_ids):
bill_values = {
'takaful_sponsorship_id': line.sponsorship_id.id if line.sponsorship_id else line.sponsorship_mechanism_id.id,
'name': self.env['ir.sequence'].next_by_code('account.move.accrsp'),
'move_type': 'in_invoice',
'journal_id': benefit_journal_id,
'date': fields.Date.today(),
'partner_id': benefit.partner_id.id,
'invoice_line_ids': [(0, 0, {
'product_id': line.product_id.id,
'price_unit': line.donation_amount,
'quantity': 1,
'name': _("Benefit Number %s") % line.sequence_no,
'analytic_account_id': line.sponsorship_id.branch_custom_id.branch.analytic_account_id.id,
})]
}
vendor_bill_vals.append(bill_values)
bill_id = self.env['account.move'].sudo().create(vendor_bill_vals)
for line in bill_id.invoice_line_ids:
line.account_id = line._get_computed_account()
taxes = line._get_computed_taxes()
if taxes and line.move_id.fiscal_position_id:
taxes = line.move_id.fiscal_position_id.map_tax(taxes, partner=line.partner_id)
line.tax_ids = taxes
line.product_uom_id = line._get_computed_uom()
if self.env.context.get('schedule_line_id'):
schedule_line = self.env['sponsorship.scheduling.line'].browse(self.env.context.get('schedule_line_id'))
schedule_line.sudo().write({'status': 'paid'})
return payments
def action_create_payments(self):
# Call the original method to create payments
@ -116,10 +269,14 @@ class AccountRegisterPayment(models.TransientModel):
payment.move_id.takaful_sponsorship_id = self.takaful_sponsorship_id.id
ctx = self.env.context.copy()
# You can update it as needed
if self._context.get('dont_redirect_to_payments'):
return True
# You can update it as needed
ctx.update({
'create': False,
})
'create': False,
})
# Prepare the action for redirection
action = {

View File

@ -0,0 +1,656 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError,UserError
import re
import requests
from datetime import datetime
from dateutil.relativedelta import relativedelta
class DonationsDetailsLines(models.Model):
_name = "donations.details.lines"
_inherit = ['mail.thread']
name = fields.Text(string="Note")
display_type = fields.Selection([
('line_section', "Section"),
('line_note', "Note")], default=False, help="Technical field for UX purpose.")
sequence = fields.Integer(string='Sequence', default=10)
sequence_no = fields.Char(default='/', readonly=True, copy=False)
donation_type = fields.Selection([('donation', 'Donation'), ('waqf', 'Waqf'), ('sponsorship', 'Sponsorship'), ],string='Donation Type')
donation_types = fields.Selection([('donation', 'Donation'), ('waqf', 'Waqf')],string='Donation Type')
product_template_id = fields.Many2one('product.template', string="Donation Name", required=True)
product_id = fields.Many2one('product.product', compute='_compute_product_id')
sponsorship_id = fields.Many2one('takaful.sponsorship', string="Sponsorship")
sponsorship_mechanism_id = fields.Many2one('takaful.sponsorship', string="Sponsorship")
sponsorship_creation_date = fields.Datetime()
donation_amount = fields.Float(string='Donation Amount', compute="_compute_donation_amount", store=True)
donation_mechanism = fields.Selection([('with_conditions', _('With Conditions')),('without_conditions', _('Without Conditions'))],string='Donation Mechanism', readonly=True)
benefit_type = fields.Selection([('orphan', 'Orphans'),('widow', 'Widows'),('both', 'Both')],string='Sponsorship Beneficiary Type',tracking=True)
sponsorship_type = fields.Selection([('person', 'Individual'),('group', 'Group')],string='Sponsorship Type',tracking=True)
gender = fields.Selection(selection=[('male', 'Male'), ('female', 'Female')], string="Gender")
age_category_id = fields.Many2one('age.category', string='Age Category')
education_status = fields.Selection(string='Education Status',selection=[('educated', 'educated'), ('illiterate', 'illiterate'),('under_study_age', 'Under Study Age')])
education_level = fields.Many2one("education.level", string='Education Levels')
members_domain_ids = fields.Many2many(comodel_name='family.member', compute='_compute_domain_ids')
benefit_id = fields.Many2one('family.member',string='Beneficiary Name',ondelete='set null',domain = "[('id', 'in',members_domain_ids)]", tracking=True)
family_id = fields.Many2one('grant.benefit',string='Family',ondelete='set null',related="benefit_id.benefit_id")
benefit_ids = fields.Many2many('family.member',string='Beneficiaries Names', tracking=True)
sponsorship_duration = fields.Selection([('temporary', 'Temporary'),('permanent', 'Permanent')],string='Sponsorship Type')
start_date = fields.Date(string="Sponsorship Start Date", copy=False)
end_date = fields.Date(string="Sponsorship End Date",compute='_compute_end_date', store=True)
payment_option = fields.Selection([('month', 'Monthly'), ('once', 'For Once')], string='Payment Option')
payment_month_count = fields.Integer(string='Payment Month Count', default=1)
fixed_value = fields.Boolean(string='Is Fixed Value?',related='product_template_id.fixed_value')
benefits_count = fields.Integer(string='Benefits Count',compute='_get_benefits_count')
total_donation_amount = fields.Float(string='Total Donation Amount',compute='_get_total_donation_amount')
sponsorships_computed = fields.Boolean(copy=False, readonly=True)
payment_method_id = fields.Many2one('takaful.payment.method', string="Payment Method", domain="[('id', 'in', allowed_payment_method_ids)]")
allowed_payment_method_ids = fields.Many2many('takaful.payment.method', compute='_compute_allowed_payment_method_ids')
payment_method = fields.Selection(related="payment_method_id.payment_method")
benefit_id_number = fields.Char("Benefit ID Number")
benefit_family_code = fields.Char("Benefit Family Code")
editable = fields.Boolean(string="Editable", default=True) # To differentiate
sponsor_id = fields.Many2one('takaful.sponsor', related='sponsorship_id.sponsor_id')
sponsor_phone = fields.Char(related='sponsorship_id.sponsor_id.mobile')
branch_custom_id = fields.Many2one('branch.settings', related='sponsorship_id.branch_custom_id')
record_type = fields.Selection([('sponsorship', 'Sponsorship'),('donation', 'Donation')], compute='_compute_record_type', store=True, readonly=True)
state = fields.Selection([('draft', 'To Pay'),('waiting', 'Waiting'), ('active', 'Active'),('closed', 'Closed'),('extended', 'Extended'), ('paid', 'Paid')], string='State', default='draft')
sponsorship_scheduling_line_ids = fields.One2many('sponsorship.scheduling.line', 'donation_detail_linked_id')
is_paid = fields.Boolean(string="Is Paid", default=False)
parent_state = fields.Char(compute='_compute_parent_state')
@api.depends('sponsorship_id','sponsorship_mechanism_id')
def _compute_parent_state(self):
for rec in self:
rec.parent_state = rec.sponsorship_id.state if rec.sponsorship_id else rec.sponsorship_mechanism_id.state
@api.depends('sponsorship_id','sponsorship_mechanism_id', 'sponsorship_id.record_type', 'sponsorship_mechanism_id.record_type')
def _compute_record_type(self):
for rec in self:
rec.record_type = rec.sponsorship_id.record_type if rec.sponsorship_id else rec.sponsorship_mechanism_id.record_type
@api.depends('sponsorship_duration')
def _compute_allowed_payment_method_ids(self):
for rec in self:
domain = []
if rec.sponsorship_duration == 'permanent':
domain.append(('payment_method', '!=', 'direct_debit'))
if rec.payment_method_id.payment_method == 'direct_debit':
rec.payment_method_id = False
rec.allowed_payment_method_ids = self.env['takaful.payment.method'].search(domain)
def _compute_product_id(self):
for rec in self:
if rec.product_template_id:
rec.product_id = rec.product_template_id._get_first_possible_variant_id()
else:
rec.product_id = False
def onset_benefit_id(self):
for rec in self:
if rec.state == 'waiting' and (rec.benefit_id or rec.benefit_ids):
rec.state = 'active'
@api.model
def run_check_all_end_dates(self):
lines = self.search([('end_date', '!=', False)])
lines.check_end_date()
@api.depends('payment_month_count', 'product_template_id')
def _compute_donation_amount(self):
for rec in self.with_context(recursive_onchanges=False):
if rec.payment_method == 'direct_debit':
rec.donation_amount = rec.product_template_id.list_price * rec.payment_month_count
elif not rec.donation_amount:
rec.donation_amount = rec.product_template_id.list_price
else:
rec.donation_amount = rec.donation_amount
def check_end_date(self):
for record in self:
if record.end_date:
today = fields.Date.today()
end_date = record.end_date
days_difference = (today - record.end_date).days
if days_difference == 30:
record.action_send_whatsapp(30,end_date)
elif days_difference == 16:
record.action_send_whatsapp(16,end_date)
elif days_difference == 4:
record.action_send_whatsapp(4,end_date)
return False
def action_send_whatsapp(self,duration,end_date):
if duration == 30:
notification = self.env['takaful.notification'].sudo().search(
[('notification_type', '=', 'before_finish'), ('message_type_ids.tool_type', '=', 'app'),('duration', '=', 30)],
limit=1
)
expiry_date_str = end_date.strftime('%d-%m-%Y') if end_date else 'N/A'
message = notification.message or _("No message found in 'Before Kafala End Date' notification.")
message = message.replace('[ExpiryDate]', expiry_date_str)
elif duration == 16:
notification = self.env['takaful.notification'].sudo().search(
[('notification_type', '=', 'before_finish'), ('message_type_ids.tool_type', '=', 'app'),('duration', '=', 16)],
limit=1
)
message = notification.message or _("No message found in 'Before Kafala End Date' notification.")
elif duration == 4:
notification = self.env['takaful.notification'].sudo().search(
[('notification_type', '=', 'before_finish'), ('message_type_ids.tool_type', '=', 'app'),('duration', '=', 4)],
limit=1
)
expiry_date_str = end_date.strftime('%d-%m-%Y') if end_date else 'N/A'
message = notification.message or _("No message found in 'Before Kafala End Date' notification.")
message = message.replace('[ExpiryDate]', expiry_date_str)
if not notification:
raise ValidationError(_("No notification of type Before Kafala End Date found."))
config = self.env['ir.config_parameter'].sudo()
account_sid = config.get_param('odex_takaful.twilio_account_sid')
auth_token = config.get_param('odex_takaful.twilio_auth_token')
from_whatsapp = config.get_param('odex_takaful.twilio_from_whatsapp')
if not account_sid or not auth_token or not from_whatsapp:
raise ValidationError(_("Twilio configuration is missing. Please configure Twilio SID, Auth Token, and WhatsApp number in General Configurations."))
from_cleaned_mobile = re.sub(r'[^\d+]', '', from_whatsapp)
from_whatsapp_number = f'whatsapp:{from_cleaned_mobile}'
# Search for the notification message for 'create_kafala'
# Use the message from the notification
amount = self.donation_amount if hasattr(self, 'donation_amount') else 0.0
for partner in self:
sponsorship_id = partner.sponsorship_mechanism_id or partner.sponsorship_id
mobile = sponsorship_id.sponsor_id.mobile if sponsorship_id.sponsor_id else sponsorship_id.member_id.mobile
if mobile:
# Clean the number (keep + and digits only)
cleaned_mobile = re.sub(r'[^\d+]', '', mobile)
to_whatsapp_number = f'whatsapp:{cleaned_mobile}'
url = f"https://api.twilio.com/2010-04-01/Accounts/{account_sid}/Messages.json"
payload = {
'From': from_whatsapp_number,
'To': to_whatsapp_number,
'Body': message,
}
response = requests.post(
url,
data=payload,
auth=(account_sid, auth_token)
)
if response.status_code != 201:
raise ValidationError(_("Failed to send WhatsApp message: %s") % response.text)
def sol_by_cart(self, operation, product_id, sponsorship_id):
"""
Create Sale Order Line By
Cart Functionality
"""
sol_object = self.env["donations.details.lines"]
if product_id.payment_method_id:
payment_method = product_id.payment_method_id.id
else:
payment_method = self.env["takaful.payment.method"].search([('payment_method', '=', 'cash')], limit=1).id
sol_data = dict()
sol_data["product_template_id"] = product_id.id
sol_data["sponsorship_id"] = sponsorship_id.id
sol_data["donation_amount"] = product_id.fixed_donation_amount
sol_data["payment_method_id"] = payment_method
sol_data["donation_types"] = 'donation'
sol_data["name"] = product_id.name
if operation == "add":
sol_object.create(sol_data)
return
sol_ = sol_object.search(
[('sponsorship_id', '=', sponsorship_id.id), ('product_template_id', '=', product_id.id), ]) # ("cart_flag", "=", True)
if operation == "remove":
sol_.unlink()
return
def user_input_qty_sol(self, _qty, product_id, sponsorship_id):
sol_object = self.env["donations.details.lines"]
product_object = self.env["product.template"]
cart_product_details = sol_object.search(
[('sponsorship_id', "=", sponsorship_id), ("product_template_id", "=", product_id)
]) # ("cart_flag", "=", True)
if product_id.payment_method_id:
payment_method = product_id.payment_method_id.id
else:
payment_method = self.env["takaful.payment.method"].search([('payment_method', '=', 'cash')], limit=1).id
donation_id = product_object.search([('id', '=', product_id)])
sol_data = dict()
sol_data["product_template_id"] = donation_id.id
sol_data["sponsorship_id"] = sponsorship_id
sol_data["donation_amount"] = donation_id.list_price
sol_data["payment_method_id"] = payment_method
sol_data["donation_types"] = 'donation'
sol_data["name"] = donation_id.name
so = sol_object.create(sol_data)
return
@api.onchange('benefit_id_number', 'sponsorship_type', 'benefit_type')
def _onchange_benefit_id_number(self):
for rec in self:
if rec.benefit_id_number and rec.sponsorship_type == 'person' and rec.benefit_type in ['orphan', 'widow']:
member = self.env['family.member'].search([('member_id_number', '=', rec.benefit_id_number)], limit=1)
if member:
rec.benefit_id = member
else:
rec.benefit_id = False
@api.constrains('payment_month_count')
def _check_payment_month_count(self):
for rec in self:
if rec.donation_type == 'sponsorship' and rec.sponsorship_duration == 'temporary' and rec.payment_option == 'month' and rec.payment_month_count <= 0:
raise ValidationError(
_("Payment Month Count should be greather than zero!")
)
@api.model
def default_get(self, fields):
res = super(DonationsDetailsLines, self).default_get(fields)
parent_id = self.env.context.get('default_active_id')
parent_record = self.env['takaful.sponsorship'].browse(parent_id)
res['start_date'] = parent_record.sponsorship_creation_date
res['sponsorship_creation_date'] = parent_record.sponsorship_creation_date
# res['donation_mechanism'] = parent_record.donation_mechanism
self._onchange_sponsorship_type()
return res
@api.onchange('donation_types', 'donation_type')
def _onchange_sponsorship_type(self):
for rec in self:
if rec.sponsorship_mechanism_id:
if rec.sponsorship_mechanism_id.record_type == 'sponsorship':
rec.donation_type = 'sponsorship'
if rec.donation_types:
if rec.donation_types == 'donation':
rec.donation_type = 'donation'
elif rec.donation_types == 'waqf':
rec.donation_type = 'waqf'
@api.onchange('payment_method_id')
def _onchange_payment_method(self):
for rec in self:
if rec.payment_method_id.payment_method == 'direct_debit':
rec.payment_option = 'month'
else:
rec.payment_option = 'once'
@api.depends('start_date', 'payment_month_count')
def _compute_end_date(self):
for record in self:
if record.start_date and record.payment_month_count:
record.end_date = record.start_date + relativedelta(months=record.payment_month_count)
else:
record.end_date = False
@api.depends('benefit_ids')
def _get_benefits_count(self):
for rec in self:
rec.benefits_count = len(rec.benefit_ids)
@api.depends('benefits_count','donation_amount')
def _get_total_donation_amount(self):
for rec in self:
if rec.sponsorship_type == 'group':
rec.total_donation_amount = rec.benefits_count * rec.donation_amount
else :
rec.total_donation_amount = rec.donation_amount
def _get_already_sponsored_beneficiaries(self, rec):
"""
Get list of beneficiary IDs that are already sponsored (active or in draft/waiting state)
to exclude them from available selection
"""
# Search for all donation lines with active sponsorships
all_lines = self.env['donations.details.lines'].sudo().search([('sponsorship_mechanism_id', '!=', False)])
# Filter for active/waiting/draft sponsorships
active_lines = all_lines.filtered(
lambda line: ((line.sponsorship_mechanism_id and line.sponsorship_mechanism_id.state == 'paid')
) and line.state == 'active'
)
# Exclude current record if editing existing line
if rec.id:
active_lines = active_lines.filtered(lambda line: line.id != rec.id)
# Get all benefit_ids from these active lines (both single and group)
sponsored_ids = active_lines.mapped('benefit_id') + active_lines.mapped('benefit_ids')
return sponsored_ids # Remove duplicates
@api.depends('gender', 'education_status', 'education_level', 'sponsorship_type', 'benefit_type', 'age_category_id', 'benefit_family_code')
def _compute_domain_ids(self):
for rec in self:
# Create a domain
rec.members_domain_ids = [(6, 0, [])]
# Get all sponsored beneficiaries (exclude them from domain)
sponsored_benefit_ids = self._get_already_sponsored_beneficiaries(rec)
if rec.benefit_family_code and rec.sponsorship_type:
benefit_ids = self.env['family.member'].search([('benefit_id.code', '=', rec.benefit_family_code)])
# Exclude already sponsored
if sponsored_benefit_ids:
benefit_ids = benefit_ids.filtered(lambda b: b not in sponsored_benefit_ids)
rec.members_domain_ids = self.env['family.member'].sudo().search([('id', 'in', benefit_ids.ids)])
domain = {'benefit_id': [('id', 'in', rec.members_domain_ids.ids)]}
return {'domain': domain}
if rec.benefit_type == 'orphan' and rec.sponsorship_type:
base_domain = \
[
'|', ('state', '=', 'second_approve'), '&',('state', 'in', ('waiting_approve', 'first_approve')), ('action_type', '=', 'suspended'),
('member_status', '=', 'benefit'),
'|',
('relationn.relation_type', '=', 'daughter'),
('relationn.relation_type', '=', 'son')
]
if rec.gender:
if rec.gender == 'female':
base_domain = [
'|', ('state', '=', 'second_approve'), '&',
('state', 'in', ('waiting_approve', 'first_approve')), ('action_type', '=', 'suspended'),
('member_status', '=', 'benefit'), ('relationn.relation_type', '=', 'daughter')]
if rec.gender == 'male':
base_domain = [
'|', ('state', '=', 'second_approve'), '&',('state', 'in', ('waiting_approve', 'first_approve')), ('action_type', '=', 'suspended'),
('member_status', '=', 'benefit'), ('relationn.relation_type', '=', 'son')]
if rec.education_status:
base_domain.append(('education_status', '=', rec.education_status))
if rec.education_level:
base_domain.append(('education_levels', '=', rec.education_level.id))
if rec.age_category_id:
base_domain.append(('age', '<=', rec.age_category_id.max_age))
base_domain.append(('age', '>=', rec.age_category_id.min_age))
# Exclude already sponsored beneficiaries
if sponsored_benefit_ids:
base_domain.append(('id', 'not in', sponsored_benefit_ids.ids))
rec.members_domain_ids = self.env['family.member'].sudo().search(base_domain)
domain = {'benefit_id': [('id', 'in', rec.members_domain_ids.ids)]}
return {'domain': domain}
if rec.benefit_type == 'widow' and rec.sponsorship_type:
base_domain = ['|',('state','=','second_approve'),'&',('state','in',('waiting_approve','first_approve')),('action_type','=','suspended'),
('member_status', '=', 'benefit'), '|', ('relationn.relation_type', '=', 'mother'),
('relationn.relation_type', '=', 'replacement_mother')]
if rec.education_status:
base_domain.append(('education_status', '=', rec.education_status))
if rec.education_level:
base_domain.append(('education_levels', '=', rec.education_level.id))
if rec.age_category_id:
base_domain.append(('age', '<=', rec.age_category_id.max_age))
base_domain.append(('age', '>=', rec.age_category_id.min_age))
# Exclude already sponsored beneficiaries
if sponsored_benefit_ids:
base_domain.append(('id', 'not in', sponsored_benefit_ids.ids))
rec.members_domain_ids = self.env['family.member'].sudo().search(base_domain)
domain = {'benefit_id': [('id', 'in', rec.members_domain_ids.ids)]}
return {'domain': domain}
if rec.benefit_type == 'both' and rec.sponsorship_type:
base_domain = ['|',('state','=','second_approve'),'&',('state','in',('waiting_approve','first_approve')),('action_type','=','suspended'),
('member_status', '=', 'benefit')]
if rec.education_status:
base_domain.append(('education_status', '=', rec.education_status))
if rec.education_level:
base_domain.append(('education_levels', '=', rec.education_level.id))
if rec.age_category_id:
base_domain.append(('age', '<=', rec.age_category_id.max_age))
base_domain.append(('age', '>=', rec.age_category_id.min_age))
# Exclude already sponsored beneficiaries
if sponsored_benefit_ids:
base_domain.append(('id', 'not in', sponsored_benefit_ids.ids))
rec.members_domain_ids = self.env['family.member'].sudo().search(base_domain)
domain = {'benefit_id': [('id', 'in', rec.members_domain_ids.ids)]}
return {'domain': domain}
@api.onchange('product_template_id','donation_types','donation_type')
def onchange_product_template_id(self):
for rec in self:
# Process both donation details and mechanism details
if rec.product_template_id.fixed_value:
rec.donation_amount = rec.product_template_id.list_price
selected_donations = []
if rec.sponsorship_id or rec.sponsorship_mechanism_id:
all_donation_lines = (
(rec.sponsorship_id.donations_details_lines if rec.sponsorship_id else self.env['donations.details.lines']) |
(rec.sponsorship_mechanism_id.donations_details_lines_mechanism_ids if rec.sponsorship_mechanism_id else self.env['donations.details.lines'])
)
selected_donations = all_donation_lines.mapped('product_template_id.id')
domain = [('id', 'not in', selected_donations),('sale_ok', '=', True)]
# Apply condition based on record type
record_type = rec.sponsorship_id.record_type if rec.sponsorship_id else rec.sponsorship_mechanism_id.record_type
if record_type == 'donation':
domain.append(('donation_category', '=', 'donation'))
elif record_type == 'sponsorship':
domain.append(('donation_category', '=', 'sponsorship'))
return {
'domain': {
'product_template_id': domain}
}
@api.onchange('donation_type')
def onchange_donation_type(self):
for rec in self:
if rec.donation_type == 'sponsorship':
rec.donation_mechanism = 'with_conditions'
def compute_sponsorships_lines(self):
for rec in self:
sponsorship_id = rec.sponsorship_mechanism_id if rec.sponsorship_mechanism_id.donations_details_lines_mechanism_ids else rec.sponsorship_id
SponsorshipSchedulingLink = self.env['sponsorship.scheduling.line'].sudo()
SponsorshipSchedulingLink.search([('sponsorship_id', 'in', sponsorship_id.ids),
('donation_detail_linked_id.sponsorships_computed', '=', False)]).unlink()
if rec.sponsorships_computed:
return
if rec.payment_option == 'month' and rec.payment_method_id.payment_method == 'direct_debit':
# Divide the total donation amount by the number of months
if rec.payment_month_count > 0:
base_amount, remainder = divmod(rec.total_donation_amount, rec.payment_month_count)
else:
raise ValidationError(
_("Payment Month Count Must be Bigger than zero!")
)
# Convert base_amount to a float with two decimal points
base_amount = float(base_amount)
for index in range(rec.payment_month_count):
# Add 1 to the base amount for the first 'remainder' months to handle the rounding issue
amount = base_amount + 1 if index < remainder else base_amount
# Format the amount to 2 decimal places for better representation
amount = round(amount, 2)
# Calculate the month and year
month_year = (rec.start_date + relativedelta(months=index)).strftime("%m/%Y")
# Create the Sponsorship Scheduling Link
SponsorshipSchedulingLink.create({
'sponsorship_id': sponsorship_id.id,
'donation_detail_linked_id': rec.id,
'beneficiary_id': rec.benefit_id.id,
'month_year': month_year,
'amount': amount
})
# else:
# month_year = (sponsorship_id.sponsorship_creation_date + relativedelta(months=1)).strftime("%m/%Y")
# # Create the Sponsorship Scheduling Link
# SponsorshipSchedulingLink.create({
# 'sponsorship_id': sponsorship_id.id,
# 'donation_detail_linked_id': rec.id,
# 'beneficiary_id': rec.benefit_id.id,
# 'month_year': month_year,
# 'amount': rec.donation_amount
# })
rec.sponsorships_computed = True
@api.model
def create(self, vals):
# Check if sequence_no is not already set
if vals.get('sequence_no', '/') == '/':
# Assign the next sequence number
vals['sequence_no'] = self.env['ir.sequence'].next_by_code('donations.details.lines.sequence') or '/'
if vals.get('display_type'):
vals.update({
'product_template_id': False,
'donation_type': False,
'payment_method_id': False
})
record = super(DonationsDetailsLines, self).create(vals)
if 'benefit_id' in vals and record.sponsorship_id:
benefit_name = record.benefit_id.name or 'None'
record.sponsorship_id.message_post(
body=f"Benefit set to <b>{benefit_name}</b> in a donation item."
)
return record
def write(self, vals):
res = False
for rec in self:
old_benefit = rec.benefit_id.name
old_benefits = rec.benefit_ids.mapped('name') # assuming benefit has a 'name'
old_benefits_set = set(old_benefits)
res |= super(DonationsDetailsLines, rec).write(vals)
rec.invalidate_cache()
rec = self.browse(rec.id)
if 'display_type' in vals and self.filtered(lambda line: line.display_type != vals.get('display_type')):
raise UserError(
_("You cannot change the type of a sale quote line. Instead you should delete the current line and create a new line of the proper type."))
if 'benefit_id' in vals:
new_benefit = rec.benefit_id.name or 'None'
sponsorship = self.env['takaful.sponsorship'].browse(
self.env.context.get('active_id'))
if sponsorship and len(sponsorship) == 1:
message = _("Benefit changed from <b>%s</b> to <b>%s</b> in a donation item.") % (
old_benefit or 'None', new_benefit)
sponsorship.message_post(body=message)
# Auto-trigger state change when benefit is assigned to waiting line
if 'benefit_ids' in vals:
new_benefits = rec.benefit_ids.mapped('name')
new_benefits_set = set(new_benefits)
added = new_benefits_set - old_benefits_set
removed = old_benefits_set - new_benefits_set
changes = []
if added:
changes.append(_("Added: <b>%s</b>") % ', '.join(added))
if removed:
changes.append(_("Removed: <b>%s</b>") % ', '.join(removed))
if changes:
# Find sponsorship
sponsorship = rec.sponsorship_id or self.env['takaful.sponsorship'].browse(
self.env.context.get('active_id'))
if sponsorship and len(sponsorship) == 1:
message = _("Benefit IDs changed in a donation item:<br/>%s") % "<br/>".join(changes)
sponsorship.message_post(body=message)
self.onset_benefit_id()
return res
def action_view_replacement_wizard(self):
self.ensure_one()
wizard = self.env['replacement.wiz'].create({
'replacement_line_ids':[(0, 0, {
'sponsorship_type': self.sponsorship_type,
'from_benefit_id': self.benefit_id.id if self.sponsorship_type == 'person' else False,
'from_benefit_ids': [(6, 0, self.benefit_ids.ids)] if self.sponsorship_type == 'group' else False
})]
})
return {
'name': 'Replacement Wizard',
'type': 'ir.actions.act_window',
'res_model': 'replacement.wiz',
'view_mode': 'form',
'res_id': wizard.id,
'target': 'new',
}
def action_view_scheduling_lines(self):
self.ensure_one()
return {
'name': 'Sponsorship Scheduling Lines',
'type': 'ir.actions.act_window',
'res_model': 'sponsorship.scheduling.line',
'view_mode': 'tree',
'domain': [('donation_detail_linked_id', '=', self.id)],
'context': {'default_donation_detail_linked_id': self.id, 'create': False, 'delete': False},
}
def action_register_payment(self):
self.ensure_one()
sponsorship_id = self.sponsorship_mechanism_id.id if self.sponsorship_mechanism_id else self.sponsorship_id.id
invoice_id = self.env['account.move'].sudo().search([('takaful_sponsorship_id', 'in', [sponsorship_id])], limit=1)
if not invoice_id:
raise ValidationError(_("No invoice found for this sponsorship."))
# payment_method_line = self.env['account.payment.method.line'].search([
# ('journal_id', '=', self.payment_method_id.journal_id.id),
# ('payment_type', '=', 'inbound')
# ], limit=1)
return {
'name': _('Register Payment'),
'res_model': 'account.payment.register',
'view_mode': 'form',
'context': {
'active_model': 'account.move',
'active_ids': invoice_id.ids,
'dont_redirect_to_payments': True,
'sponsorship_line_ids': self.ids,
'default_journal_id': self.payment_method_id.journal_id.id,
'default_amount': self.total_donation_amount,
# 'default_payment_method_line_id': payment_method_line.id,
},
'target': 'new',
'type': 'ir.actions.act_window',
}

View File

@ -8,4 +8,24 @@ class ProductTemplate(models.Model):
def _compute_fixed_value(self):
for rec in self:
rec.fixed_value = False
rec.fixed_value = False
def link_to_sponsorship(self):
sponsorship_id = self.env.context.get('sponsorship_id')
if not sponsorship_id:
return
sponsorship = self.env['takaful.sponsorship'].browse(sponsorship_id)
vals = []
for rec in self:
vals.append((0, 0, {
'product_template_id': rec.id,
'sponsorship_id': sponsorship_id,
'donation_types': 'donation',
'donation_mechanism': 'without_conditions',
}))
# Append to existing lines instead of replacing them
sponsorship.write({
'donations_details_lines': vals
})

View File

@ -12,6 +12,7 @@ from dateutil.parser import parse
from math import floor
import re
import requests
from odoo.osv import expression
@ -45,8 +46,10 @@ class TakafulSponsorship(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Sponsorship"
_rec_name = 'code'
_order = 'id desc'
sponsor_id = fields.Many2one('takaful.sponsor',string='Sponsor Name',domain="[('branch_custom_id', '=', branch_custom_id),('branch_custom_id','!=',False)]")
sponsor_id = fields.Many2one('takaful.sponsor',string='Sponsor Name',domain="[('id', 'in', allowed_sponsor_ids)]")
allowed_sponsor_ids = fields.Many2many('takaful.sponsor', compute='_compute_allowed_sponsor_ids')
member_id = fields.Many2one('res.partner',string='Member Name',domain="[('is_member', '=', True)]")
is_gift = fields.Selection([('no', 'No'),('yes', 'Yes')],string='Is Gift To Person')
gifter_id = fields.Many2one(
@ -70,7 +73,7 @@ class TakafulSponsorship(models.Model):
sponsor_title = fields.Many2one('res.partner.title',string='Sponsor Title')
donate_for_another_person = fields.Boolean(string='Donate For Another Person')
another_sponsors = fields.One2many('donate.for.another.person','sponsorship_id')
registered_type = fields.Selection(string='Registered Type',selection=[('sponsor', 'Sponsor'),('member', 'Member')])
registered_type = fields.Selection(string='Registered Type',selection=[('sponsor', 'Sponsor'),('member', 'Member'), ('donor', 'Donor')])
members_domain_ids = fields.Many2many(comodel_name='family.member', compute='_compute_domain_ids')
# This is if for one person, and not a group.
benefit_id = fields.Many2one('family.member',string='Beneficiary Name',ondelete='set null',domain = "[('id', 'in',members_domain_ids)]")
@ -109,7 +112,8 @@ class TakafulSponsorship(models.Model):
journal_entry_ids = fields.One2many('account.move', 'takaful_sponsorship_id')
journal_entry_count = fields.Integer(compute="_compute_journal_entry_count")
refund_payment_ids = fields.One2many('account.payment', 'takaful_sponsorship_id')
journal_entry_count_vendor = fields.Integer(compute="_compute_journal_entry_count_vendor")
payment_ids = fields.One2many('account.payment', 'takaful_sponsorship_id')
refund_payment_count = fields.Integer(compute="_compute_refund_payment_count")
related_move_records_count = fields.Integer(compute="_compute_related_move_records")
related_move_lines_records_count = fields.Integer(compute="_compute_related_move_lines_records")
@ -122,7 +126,22 @@ class TakafulSponsorship(models.Model):
replaced = fields.Boolean('Replaced')
replacement_id = fields.Many2one('replacement.process', string="Replacement")
create_uid = fields.Many2one('res.users', default=lambda self: self.env.user)
marketer_id = fields.Many2one('hr.employee')
@api.onchange('branch_custom_id', 'registered_type')
def _compute_allowed_sponsor_ids(self):
for rec in self:
if rec.branch_custom_id and rec.registered_type:
domain = []
if rec.registered_type == 'sponsor':
expression.AND([domain, [('is_sponsor_portal', '=', True)]])
elif rec.registered_type == 'member':
expression.AND([domain, [('is_member', '=', True)]])
elif rec.registered_type == 'donor':
expression.AND([domain, ['|', '|', ('is_member', '=', True), ('is_donor', '=', True),('is_sponsor_portal', '=', True)]])
rec.allowed_sponsor_ids = self.env['takaful.sponsor'].search(domain)
else:
rec.allowed_sponsor_ids = self.env['takaful.sponsor']
def compute_days_after_payment(self):
"""Check if the number of hours passed after payment is within the configured limit"""
@ -296,37 +315,21 @@ class TakafulSponsorship(models.Model):
return invoice
def donation_catelog(self):
cart_object = self.env["donations.details.lines"]
donation_object = self.env["donations.items"]
cart_products_details = cart_object.search(
[('sponsorship_id', "=", self.id)]) # , ("cart_flag", "=", True)
product_object_data = donation_object.search(
[("_quantity", "!=", 0)])
assign_quantity = 0
for rec in product_object_data:
rec._quantity = 0
if len(cart_products_details) > 0:
for rec in cart_products_details:
if assign_quantity == 0:
assign_quantity += 1
kanban_view = self.env.ref(
'odex_takaful.product_product_view_kanban')
def action_open_donation_catalog(self):
all_donation_lines = self.donations_details_lines | self.donations_details_lines_mechanism_ids
return {
'type': 'ir.actions.act_window',
'name': _('Choose Donation Items'),
'res_model': 'product.template',
'views': [(kanban_view.id, 'kanban')],
'domain': [('id', 'not in', all_donation_lines.mapped('product_template_id').ids),('donation_category','=','donation'),],
'views': [(False, 'kanban')],
'domain': [('id', 'not in', all_donation_lines.mapped('product_template_id').ids),('donation_category','=','donation')],
'target': 'new',
'context': {
# '_quantity_change': True,
'sponsorship_id': self.id,
'create': False
},
'help': _("""<p class="o_view_nocontent_smiling_face">
Create a new product
</p>""")
@ -402,7 +405,12 @@ class TakafulSponsorship(models.Model):
@api.depends('journal_entry_ids')
def _compute_journal_entry_count(self):
for rec in self:
rec.journal_entry_count = len(rec.journal_entry_ids)
rec.journal_entry_count = len(rec.journal_entry_ids.filtered(lambda r: r.move_type == 'out_invoice'))
@api.depends('journal_entry_ids')
def _compute_journal_entry_count_vendor(self):
for rec in self:
rec.journal_entry_count_vendor = len(rec.journal_entry_ids.filtered(lambda r: r.move_type == 'in_invoice'))
@api.depends('donations_details_lines')
def _compute_donation_count(self):
@ -412,22 +420,32 @@ class TakafulSponsorship(models.Model):
def action_view_journal_entries(self):
self.ensure_one() # Ensure the method is called on a single record
action = self.env.ref('account.action_move_out_invoice_type').read()[0]
action['domain'] = [('id', 'in', self.journal_entry_ids.ids)]
action['domain'] = [('move_type', '=', 'out_invoice'), ('id', 'in', self.journal_entry_ids.ids)]
action['context'] = {
'default_takaful_sponsorship_id': self.id,
'create': False
}
return action
def action_view_journal_entries_vendor(self):
self.ensure_one() # Ensure the method is called on a single record
action = self.env.ref('account.action_move_out_invoice_type').read()[0]
action['domain'] = [('move_type', '=', 'in_invoice'), ('id', 'in', self.journal_entry_ids.ids)]
action['context'] = {
'default_takaful_sponsorship_id': self.id,
'create': False
}
return action
@api.depends('refund_payment_ids')
@api.depends('payment_ids')
def _compute_refund_payment_count(self):
for rec in self:
rec.refund_payment_count = len(rec.refund_payment_ids)
rec.refund_payment_count = len(rec.payment_ids)
def action_view_refund_payments(self):
self.ensure_one() # Ensure the method is called on a single record
action = self.env.ref('account.action_account_payments').read()[0]
action['domain'] = [('id', 'in', self.refund_payment_ids.ids)]
action['domain'] = [('id', 'in', self.payment_ids.ids)]
action['context'] = {
'default_takaful_sponsorship_id': self.id,
'create': False
@ -454,6 +472,22 @@ class TakafulSponsorship(models.Model):
'domain': [('id', 'in', moves)],
}
def action_open_donation_catalog(self):
""" Opens product catalog to link products to sponsorship """
self.ensure_one()
return {
'name': _('Donation Catalog'),
'type': 'ir.actions.act_window',
'res_model': 'product.template',
'view_mode': 'tree',
'view_id': self.env.ref('odex_takaful.view_product_template_tree_sponsorship').id,
'context': {
'sponsorship_id': self.id, 'create': False, 'edit': False, 'delete': False,
},
'domain': [('donation_category', '=', 'donation'), ('id', 'not in', self.donations_details_lines.mapped('product_template_id').ids)],
'target': 'new',
}
def _compute_related_move_lines_records(self):
for record in self:
record.related_move_lines_records_count = len(self.env['account.move'].search([
@ -1224,18 +1258,19 @@ class TakafulSponsorship(models.Model):
_("Please add at least one line in donation details!")
)
if not len(self.donations_details_lines_mechanism_ids.payment_method_id) and not len(self.donations_details_lines.payment_method_id):
raise ValidationError(
_("Please define the payment method for each line!")
)
# Process both donation details and mechanism details
all_donation_lines = self.donations_details_lines | self.donations_details_lines_mechanism_ids
all_donation_lines = all_donation_lines.filtered(lambda r: r.display_type == False)
# Calculate sponsorships lines automatically for no sponsorship donation type
all_donation_lines.filtered(lambda r: r.donation_type != 'sponsorship').compute_sponsorships_lines()
if not all(all_donation_lines.mapped("sponsorships_computed")):
raise ValidationError(
_("Please schedule donations items for confirmation and continuity!")
)
(self.donations_details_lines + self.donations_details_lines_mechanism_ids).compute_sponsorships_lines()
if self.state == 'draft':
# Send SMS Notification
@ -1257,14 +1292,6 @@ class TakafulSponsorship(models.Model):
})
push.sudo().send_sms_notification()
for donation in all_donation_lines:
if donation.sponsorship_type == 'group':
for benefit in donation.benefit_ids:
benefit.sponsor_related_id = self.sponsor_id.id
benefit.sponsorship_id = self.id
if donation.sponsorship_type == 'person':
donation.benefit_id.sponsor_related_id = self.sponsor_id.id
donation.benefit_id.sponsorship_id = self.id
self.state = "confirmed"
if self.sponsor_or_donor_type != 'registered':
@ -1284,14 +1311,22 @@ class TakafulSponsorship(models.Model):
'partner_id': self.sponsor_id.sponsor_partner_id.id,
'invoice_line_ids': [(0, 0, {
'product_id': line.product_id.id,
'quantity': 1,
'price_unit': line.total_donation_amount,
'quantity': 1,
'name': _("Sponsorship Entitlement Number %s") % line.sequence_no,
'analytic_account_id': line.sponsorship_id.branch_custom_id.branch.analytic_account_id.id,
}) for line in all_donation_lines],
# 'branch_id': TODO there is no res.branch in takaful.sponsorship model
}
invoice_id = Invoice.create(invoice_values)
for line in invoice_id.invoice_line_ids:
line.account_id = line._get_computed_account()
taxes = line._get_computed_taxes()
if taxes and line.move_id.fiscal_position_id:
taxes = line.move_id.fiscal_position_id.map_tax(taxes, partner=line.partner_id)
line.tax_ids = taxes
line.product_uom_id = line._get_computed_uom()
invoice_id.action_post()
# Search for the notification message for 'create_kafala'
@ -1659,28 +1694,32 @@ class TakafulSponsorship(models.Model):
}, }
def action_register_payment(self):
invoice_id = self.env['account.move'].sudo().search([('takaful_sponsorship_id', '=', self.id)], limit=1)
if not invoice_id:
raise ValidationError(_("No invoice found for this sponsorship."))
sponsorship_line_ids = (self.donations_details_lines + self.donations_details_lines_mechanism_ids).filtered(lambda l: l.state == 'draft')
amount = sum(sponsorship_line_ids.mapped('total_donation_amount'))
return {
'name': _('Register Payment'),
'res_model': 'account.payment.register',
'view_mode': 'form',
'context': {
'active_model': 'account.move',
'active_ids': invoice_id.ids,
'default_amount': amount,
'default_journal_id': sponsorship_line_ids.payment_method_id[-1].journal_id.id,
'dont_redirect_to_payments': True,
'sponsorship_line_ids': sponsorship_line_ids.ids,
},
'target': 'new',
'type': 'ir.actions.act_window',
}
show_register_payment = fields.Boolean(string='Show Register Payment', compute='_compute_show_register_payment')
class AccountVoucher(models.Model):
_inherit = "account.move"
sponsorship_id = fields.Many2one('takaful.sponsorship',string='Sponsorship', readonly=True,)
payment_id = fields.Many2one('account.payment',string='Payment', copy=False)
# @api.multi
def action_move_line_create(self):
'''
Confirm the vouchers given in ids and create the journal entries for each of them
'''
res = super(AccountVoucher, self).action_move_line_create()
if res :
self.write({'payment_id':self.move_id.line_ids.mapped('payment_id').id})
@api.onchange('payment_journal_id')
def on_change_payment_journal_id(self):
def _compute_show_register_payment(self):
for rec in self:
rec.account_id = rec.payment_journal_id.default_debit_account_id
rec.show_register_payment = len(((rec.donations_details_lines + rec.donations_details_lines_mechanism_ids).filtered(lambda l: l.state == 'draft')).payment_method_id) <= 1
class AnotherSponsors(models.Model):
_name = "donate.for.another.person"
@ -1692,589 +1731,6 @@ class AnotherSponsors(models.Model):
receive_messages = fields.Boolean(string='Receive messages?')
note = fields.Char(string='Note')
class DonationsDetailsLines(models.Model):
_name = "donations.details.lines"
_inherit = ['mail.thread']
name = fields.Text(string="Note")
display_type = fields.Selection([
('line_section', "Section"),
('line_note', "Note")], default=False, help="Technical field for UX purpose.")
sequence = fields.Integer(string='Sequence', default=10)
sequence_no = fields.Char(default='/', readonly=True, copy=False)
donation_type = fields.Selection([('donation', 'Donation'), ('waqf', 'Waqf'), ('sponsorship', 'Sponsorship'), ],string='Donation Type')
donation_types = fields.Selection([('donation', 'Donation'), ('waqf', 'Waqf')],string='Donation Type')
product_template_id = fields.Many2one('product.template', string="Donation Name", required=True)
product_id = fields.Many2one('product.product', compute='_compute_product_id')
sponsorship_id = fields.Many2one('takaful.sponsorship', string="Sponsorship")
sponsorship_mechanism_id = fields.Many2one('takaful.sponsorship', string="Sponsorship")
sponsorship_creation_date = fields.Datetime()
donation_amount = fields.Float(string='Donation Amount', compute="_compute_donation_amount", store=True)
donation_mechanism = fields.Selection([('with_conditions', _('With Conditions')),('without_conditions', _('Without Conditions'))],string='Donation Mechanism', readonly=True)
benefit_type = fields.Selection([('orphan', 'Orphans'),('widow', 'Widows'),('both', 'Both')],string='Sponsorship Beneficiary Type',tracking=True)
sponsorship_type = fields.Selection([('person', 'Individual'),('group', 'Group')],string='Sponsorship Type',tracking=True)
gender = fields.Selection(selection=[('male', 'Male'), ('female', 'Female')], string="Gender")
age_category_id = fields.Many2one('age.category', string='Age Category')
education_status = fields.Selection(string='Education Status',selection=[('educated', 'educated'), ('illiterate', 'illiterate'),('under_study_age', 'Under Study Age')])
education_level = fields.Many2one("education.level", string='Education Levels')
members_domain_ids = fields.Many2many(comodel_name='family.member', compute='_compute_domain_ids')
benefit_id = fields.Many2one('family.member',string='Beneficiary Name',ondelete='set null',domain = "[('id', 'in',members_domain_ids)]", tracking=True)
family_id = fields.Many2one('grant.benefit',string='Family',ondelete='set null',related="benefit_id.benefit_id")
benefit_ids = fields.Many2many('family.member',string='Beneficiaries Names', tracking=True)
sponsorship_duration = fields.Selection([('temporary', 'Temporary'),('permanent', 'Permanent')],string='Sponsorship Type')
start_date = fields.Date(string="Sponsorship Start Date", copy=False)
end_date = fields.Date(string="Sponsorship End Date",compute='_compute_end_date', store=True)
payment_option = fields.Selection([('month', 'Monthly'), ('once', 'For Once')], string='Payment Option')
payment_month_count = fields.Integer(string='Payment Month Count', default=1)
fixed_value = fields.Boolean(string='Is Fixed Value?',related='product_template_id.fixed_value')
benefits_count = fields.Integer(string='Benefits Count',compute='_get_benefits_count')
total_donation_amount = fields.Float(string='Total Donation Amount',compute='_get_total_donation_amount')
sponsorships_computed = fields.Boolean(copy=False, readonly=True)
payment_method_id = fields.Many2one('takaful.payment.method', string="Payment Method", domain="[('id', 'in', allowed_payment_method_ids)]")
allowed_payment_method_ids = fields.Many2many('takaful.payment.method', compute='_compute_allowed_payment_method_ids')
payment_method = fields.Selection(related="payment_method_id.payment_method")
benefit_id_number = fields.Char("Benefit ID Number")
benefit_family_code = fields.Char("Benefit Family Code")
editable = fields.Boolean(string="Editable", default=True) # To differentiate
sponsor_id = fields.Many2one('takaful.sponsor', related='sponsorship_id.sponsor_id')
sponsor_phone = fields.Char(related='sponsorship_id.sponsor_id.mobile')
branch_custom_id = fields.Many2one('branch.settings', related='sponsorship_id.branch_custom_id')
record_type = fields.Selection(related='sponsorship_id.record_type', store=True, readonly=True)
state = fields.Selection([('draft', 'To Pay'),('waiting', 'Waiting'), ('active', 'Active'),('closed', 'Closed'),('extended', 'Extended')], string='State', default='draft')
sponsorship_scheduling_line_ids = fields.One2many('sponsorship.scheduling.line', 'donation_detail_linked_id')
@api.onchange('sponsorship_duration')
def _compute_allowed_payment_method_ids(self):
for rec in self:
domain = []
if rec.sponsorship_duration == 'permanent':
domain.append(('payment_method', '!=', 'direct_debit'))
rec.allowed_payment_method_ids = self.env['takaful.payment.method'].search(domain)
def _compute_product_id(self):
for rec in self:
if rec.product_template_id:
rec.product_id = rec.product_template_id._get_first_possible_variant_id()
else:
rec.product_id = False
def onset_benefit_id(self):
for rec in self:
if rec.state == 'waiting' and rec.benefit_id:
rec.state = 'active'
@api.model
def run_check_all_end_dates(self):
lines = self.search([('end_date', '!=', False)])
lines.check_end_date()
@api.depends('payment_month_count')
def _compute_donation_amount(self):
for rec in self:
rec.donation_amount = rec.product_template_id.list_price * rec.payment_month_count
def check_end_date(self):
for record in self:
if record.end_date:
today = fields.Date.today()
end_date = record.end_date
days_difference = (today - record.end_date).days
if days_difference == 30:
record.action_send_whatsapp(30,end_date)
elif days_difference == 16:
record.action_send_whatsapp(16,end_date)
elif days_difference == 4:
record.action_send_whatsapp(4,end_date)
return False
def action_send_whatsapp(self,duration,end_date):
if duration == 30:
notification = self.env['takaful.notification'].sudo().search(
[('notification_type', '=', 'before_finish'), ('message_type_ids.tool_type', '=', 'app'),('duration', '=', 30)],
limit=1
)
expiry_date_str = end_date.strftime('%d-%m-%Y') if end_date else 'N/A'
message = notification.message or _("No message found in 'Before Kafala End Date' notification.")
message = message.replace('[ExpiryDate]', expiry_date_str)
elif duration == 16:
notification = self.env['takaful.notification'].sudo().search(
[('notification_type', '=', 'before_finish'), ('message_type_ids.tool_type', '=', 'app'),('duration', '=', 16)],
limit=1
)
message = notification.message or _("No message found in 'Before Kafala End Date' notification.")
elif duration == 4:
notification = self.env['takaful.notification'].sudo().search(
[('notification_type', '=', 'before_finish'), ('message_type_ids.tool_type', '=', 'app'),('duration', '=', 4)],
limit=1
)
expiry_date_str = end_date.strftime('%d-%m-%Y') if end_date else 'N/A'
message = notification.message or _("No message found in 'Before Kafala End Date' notification.")
message = message.replace('[ExpiryDate]', expiry_date_str)
if not notification:
raise ValidationError(_("No notification of type Before Kafala End Date found."))
config = self.env['ir.config_parameter'].sudo()
account_sid = config.get_param('odex_takaful.twilio_account_sid')
auth_token = config.get_param('odex_takaful.twilio_auth_token')
from_whatsapp = config.get_param('odex_takaful.twilio_from_whatsapp')
if not account_sid or not auth_token or not from_whatsapp:
raise ValidationError(_("Twilio configuration is missing. Please configure Twilio SID, Auth Token, and WhatsApp number in General Configurations."))
from_cleaned_mobile = re.sub(r'[^\d+]', '', from_whatsapp)
from_whatsapp_number = f'whatsapp:{from_cleaned_mobile}'
# Search for the notification message for 'create_kafala'
# Use the message from the notification
amount = self.donation_amount if hasattr(self, 'donation_amount') else 0.0
for partner in self:
sponsorship_id = partner.sponsorship_mechanism_id or partner.sponsorship_id
mobile = sponsorship_id.sponsor_id.mobile if sponsorship_id.sponsor_id else sponsorship_id.member_id.mobile
if mobile:
# Clean the number (keep + and digits only)
cleaned_mobile = re.sub(r'[^\d+]', '', mobile)
to_whatsapp_number = f'whatsapp:{cleaned_mobile}'
url = f"https://api.twilio.com/2010-04-01/Accounts/{account_sid}/Messages.json"
payload = {
'From': from_whatsapp_number,
'To': to_whatsapp_number,
'Body': message,
}
response = requests.post(
url,
data=payload,
auth=(account_sid, auth_token)
)
if response.status_code != 201:
raise ValidationError(_("Failed to send WhatsApp message: %s") % response.text)
def donation_catelog(self):
cart_object = self.env["donations.details.lines"]
donation_object = self.env["product.template"]
sponsorship_id = self.env['takaful.sponsorship'].browse(self.env.context.get('default_active_id'))
cart_products_details = cart_object.search(
[('sponsorship_id', "=", sponsorship_id.id)]) # , ("cart_flag", "=", True)
product_object_data = donation_object.search(
[("_quantity", "!=", 0)])
assign_quantity = 0
for rec in product_object_data:
rec._quantity = 0
if len(cart_products_details) > 0:
for rec in cart_products_details:
if assign_quantity == 0:
assign_quantity += 1
kanban_view = self.env.ref(
'odex_takaful.product_product_view_kanban')
all_donation_lines = self.sponsorship_id.donations_details_lines
return {
'type': 'ir.actions.act_window',
'name': _('Choose Donation Items'),
'res_model': 'donations.items',
'views': [(kanban_view.id, 'kanban')],
'domain': [('id', 'not in', all_donation_lines.mapped('product_template_id').ids), ('show_donation_item','=',True), ('donation_type','=',self.donation_types)],
'context': {
# '_quantity_change': True,
'sponsorship_id': sponsorship_id.id,
'create': False
},
'help': _("""<p class="o_view_nocontent_smiling_face">
Create a new product
</p>""")
}
def sol_by_cart(self, operation, product_id, sponsorship_id):
"""
Create Sale Order Line By
Cart Functionality
"""
sol_object = self.env["donations.details.lines"]
if product_id.payment_method_id:
payment_method = product_id.payment_method_id.id
else:
payment_method = self.env["takaful.payment.method"].search([('payment_method', '=', 'cash')], limit=1).id
sol_data = dict()
sol_data["product_template_id"] = product_id.id
sol_data["sponsorship_id"] = sponsorship_id.id
sol_data["donation_amount"] = product_id.fixed_donation_amount
sol_data["payment_method_id"] = payment_method
sol_data["donation_types"] = 'donation'
sol_data["name"] = product_id.name
if operation == "add":
sol_object.create(sol_data)
return
sol_ = sol_object.search(
[('sponsorship_id', '=', sponsorship_id.id), ('product_template_id', '=', product_id.id), ]) # ("cart_flag", "=", True)
if operation == "remove":
sol_.unlink()
return
def user_input_qty_sol(self, _qty, product_id, sponsorship_id):
sol_object = self.env["donations.details.lines"]
product_object = self.env["product.template"]
cart_product_details = sol_object.search(
[('sponsorship_id', "=", sponsorship_id), ("product_template_id", "=", product_id)
]) # ("cart_flag", "=", True)
if product_id.payment_method_id:
payment_method = product_id.payment_method_id.id
else:
payment_method = self.env["takaful.payment.method"].search([('payment_method', '=', 'cash')], limit=1).id
donation_id = product_object.search([('id', '=', product_id)])
sol_data = dict()
sol_data["product_template_id"] = donation_id.id
sol_data["sponsorship_id"] = sponsorship_id
sol_data["donation_amount"] = donation_id.list_price
sol_data["payment_method_id"] = payment_method
sol_data["donation_types"] = 'donation'
sol_data["name"] = donation_id.name
so = sol_object.create(sol_data)
return
@api.onchange('benefit_id_number', 'sponsorship_type', 'benefit_type')
def _onchange_benefit_id_number(self):
for rec in self:
if rec.benefit_id_number and rec.sponsorship_type == 'person' and rec.benefit_type in ['orphan', 'widow']:
member = self.env['family.member'].search([('member_id_number', '=', rec.benefit_id_number)], limit=1)
if member:
rec.benefit_id = member
else:
rec.benefit_id = False
@api.constrains('payment_month_count')
def _check_payment_month_count(self):
for rec in self:
if rec.donation_type == 'sponsorship' and rec.sponsorship_duration == 'temporary' and rec.payment_option == 'month' and rec.payment_month_count <= 0:
raise ValidationError(
_("Payment Month Count should be greather than zero!")
)
@api.model
def default_get(self, fields):
res = super(DonationsDetailsLines, self).default_get(fields)
parent_id = self.env.context.get('default_active_id')
parent_record = self.env['takaful.sponsorship'].browse(parent_id)
res['start_date'] = parent_record.sponsorship_creation_date
res['sponsorship_creation_date'] = parent_record.sponsorship_creation_date
# res['donation_mechanism'] = parent_record.donation_mechanism
self._onchange_sponsorship_type()
return res
@api.onchange('donation_types', 'donation_type')
def _onchange_sponsorship_type(self):
for rec in self:
if rec.sponsorship_mechanism_id:
if rec.sponsorship_mechanism_id.record_type == 'sponsorship':
rec.donation_type = 'sponsorship'
if rec.donation_types:
if rec.donation_types == 'donation':
rec.donation_type = 'donation'
elif rec.donation_types == 'waqf':
rec.donation_type = 'waqf'
@api.onchange('payment_method_id')
def _onchange_payment_method(self):
for rec in self:
if rec.payment_method_id.payment_method == 'direct_debit':
rec.payment_option = 'month'
else:
rec.payment_option = 'once'
@api.depends('start_date', 'payment_month_count')
def _compute_end_date(self):
for record in self:
if record.start_date and record.payment_month_count:
record.end_date = record.start_date + relativedelta(months=record.payment_month_count)
else:
record.end_date = False
@api.depends('benefit_ids')
def _get_benefits_count(self):
for rec in self:
rec.benefits_count = len(rec.benefit_ids)
@api.depends('benefits_count','donation_amount')
def _get_total_donation_amount(self):
for rec in self:
if rec.sponsorship_type == 'group':
rec.total_donation_amount = rec.benefits_count * rec.donation_amount
else :
rec.total_donation_amount = rec.donation_amount
@api.depends('gender', 'education_status', 'education_level', 'sponsorship_type', 'benefit_type', 'age_category_id', 'benefit_family_code')
def _compute_domain_ids(self):
for rec in self:
# Create a domain
rec.members_domain_ids = [(6, 0, [])]
if rec.benefit_family_code and rec.sponsorship_type:
benefit_ids = self.env['family.member'].search([('benefit_id.code', '=', rec.benefit_family_code)])
rec.members_domain_ids = self.env['family.member'].sudo().search([('id', 'in', benefit_ids.ids)])
domain = {'benefit_id': [('id', 'in', rec.members_domain_ids.ids)]}
return {'domain': domain}
if rec.benefit_type == 'orphan' and rec.sponsorship_type:
base_domain = \
[
'|', ('state', '=', 'second_approve'), '&',('state', 'in', ('waiting_approve', 'first_approve')), ('action_type', '=', 'suspended'),
('member_status', '=', 'benefit'),
'|',
('relationn.relation_type', '=', 'daughter'),
('relationn.relation_type', '=', 'son')
]
if rec.gender:
if rec.gender == 'female':
base_domain = [
'|', ('state', '=', 'second_approve'), '&',
('state', 'in', ('waiting_approve', 'first_approve')), ('action_type', '=', 'suspended'),
('member_status', '=', 'benefit'), ('relationn.relation_type', '=', 'daughter')]
if rec.gender == 'male':
base_domain = [
'|', ('state', '=', 'second_approve'), '&',('state', 'in', ('waiting_approve', 'first_approve')), ('action_type', '=', 'suspended'),
('member_status', '=', 'benefit'), ('relationn.relation_type', '=', 'son')]
if rec.education_status:
base_domain.append(('education_status', '=', rec.education_status))
if rec.education_level:
base_domain.append(('education_levels', '=', rec.education_level.id))
if rec.age_category_id:
base_domain.append(('age', '<=', rec.age_category_id.max_age))
base_domain.append(('age', '>=', rec.age_category_id.min_age))
rec.members_domain_ids = self.env['family.member'].sudo().search(base_domain)
domain = {'benefit_id': [('id', 'in', rec.members_domain_ids.ids)]}
return {'domain': domain}
if rec.benefit_type == 'widow' and rec.sponsorship_type:
base_domain = ['|',('state','=','second_approve'),'&',('state','in',('waiting_approve','first_approve')),('action_type','=','suspended'),
('member_status', '=', 'benefit'), '|', ('relationn.relation_type', '=', 'mother'),
('relationn.relation_type', '=', 'replacement_mother')]
if rec.education_status:
base_domain.append(('education_status', '=', rec.education_status))
if rec.education_level:
base_domain.append(('education_levels', '=', rec.education_level.id))
if rec.age_category_id:
base_domain.append(('age', '<=', rec.age_category_id.max_age))
base_domain.append(('age', '>=', rec.age_category_id.min_age))
rec.members_domain_ids = self.env['family.member'].sudo().search(base_domain)
domain = {'benefit_id': [('id', 'in', rec.members_domain_ids.ids)]}
return {'domain': domain}
if rec.benefit_type == 'both' and rec.sponsorship_type:
base_domain = ['|',('state','=','second_approve'),'&',('state','in',('waiting_approve','first_approve')),('action_type','=','suspended'),
('member_status', '=', 'benefit')]
if rec.education_status:
base_domain.append(('education_status', '=', rec.education_status))
if rec.education_level:
base_domain.append(('education_levels', '=', rec.education_level.id))
if rec.age_category_id:
base_domain.append(('age', '<=', rec.age_category_id.max_age))
base_domain.append(('age', '>=', rec.age_category_id.min_age))
rec.members_domain_ids = self.env['family.member'].sudo().search(base_domain)
domain = {'benefit_id': [('id', 'in', rec.members_domain_ids.ids)]}
return {'domain': domain}
@api.onchange('product_template_id','donation_types','donation_type')
def onchange_product_template_id(self):
for rec in self:
# Process both donation details and mechanism details
if rec.product_template_id.fixed_value:
rec.donation_amount = rec.product_template_id.list_price
selected_donations = []
if rec.sponsorship_id or rec.sponsorship_mechanism_id:
all_donation_lines = (
(rec.sponsorship_id.donations_details_lines if rec.sponsorship_id else self.env['donations.details.lines']) |
(rec.sponsorship_mechanism_id.donations_details_lines_mechanism_ids if rec.sponsorship_mechanism_id else self.env['donations.details.lines'])
)
selected_donations = all_donation_lines.mapped('product_template_id.id')
domain = [('id', 'not in', selected_donations),('sale_ok', '=', True)]
# Apply condition based on record type
record_type = rec.sponsorship_id.record_type if rec.sponsorship_id else rec.sponsorship_mechanism_id.record_type
if record_type == 'donation':
domain.append(('donation_category', '=', 'donation'))
elif record_type == 'sponsorship':
domain.append(('donation_category', '=', 'sponsorship'))
return {
'domain': {
'product_template_id': domain}
}
@api.onchange('donation_type')
def onchange_donation_type(self):
for rec in self:
if rec.donation_type == 'sponsorship':
rec.donation_mechanism = 'with_conditions'
def compute_sponsorships_lines(self):
for rec in self:
sponsorship_id = rec.sponsorship_mechanism_id if rec.sponsorship_mechanism_id.donations_details_lines_mechanism_ids else rec.sponsorship_id
SponsorshipSchedulingLink = self.env['sponsorship.scheduling.line'].sudo()
SponsorshipSchedulingLink.search([('sponsorship_id', 'in', sponsorship_id.ids),
('donation_detail_linked_id.sponsorships_computed', '=', False)]).unlink()
if rec.sponsorships_computed:
return
if rec.payment_option == 'month':
# Divide the total donation amount by the number of months
if rec.payment_month_count > 0:
base_amount, remainder = divmod(rec.donation_amount, rec.payment_month_count)
else:
raise ValidationError(
_("Payment Month Count Must be Bigger than zero!")
)
# Convert base_amount to a float with two decimal points
base_amount = float(base_amount)
for index in range(rec.payment_month_count):
# Add 1 to the base amount for the first 'remainder' months to handle the rounding issue
amount = base_amount + 1 if index < remainder else base_amount
# Format the amount to 2 decimal places for better representation
amount = round(amount, 2)
# Calculate the month and year
month_year = (rec.start_date + relativedelta(months=index)).strftime("%m/%Y")
# Create the Sponsorship Scheduling Link
SponsorshipSchedulingLink.create({
'sponsorship_id': sponsorship_id.id,
'donation_detail_linked_id': rec.id,
'beneficiary_id': rec.benefit_id.id,
'month_year': month_year,
'amount': amount
})
else:
month_year = (sponsorship_id.sponsorship_creation_date + relativedelta(months=1)).strftime("%m/%Y")
# Create the Sponsorship Scheduling Link
SponsorshipSchedulingLink.create({
'sponsorship_id': sponsorship_id.id,
'donation_detail_linked_id': rec.id,
'beneficiary_id': rec.benefit_id.id,
'month_year': month_year,
'amount': rec.donation_amount
})
rec.sponsorships_computed = True
@api.model
def create(self, vals):
# Check if sequence_no is not already set
if vals.get('sequence_no', '/') == '/':
# Assign the next sequence number
vals['sequence_no'] = self.env['ir.sequence'].next_by_code('donations.details.lines.sequence') or '/'
if vals.get('display_type'):
vals.update({
'product_template_id': False,
'donation_type': False,
'payment_method_id': False
})
record = super(DonationsDetailsLines, self).create(vals)
if 'benefit_id' in vals and record.sponsorship_id:
benefit_name = record.benefit_id.name or 'None'
record.sponsorship_id.message_post(
body=f"Benefit set to <b>{benefit_name}</b> in a donation item."
)
return record
def write(self, vals):
for rec in self:
old_benefit = rec.benefit_id.name
old_benefits = rec.benefit_ids.mapped('name') # assuming benefit has a 'name'
old_benefits_set = set(old_benefits)
res = super(DonationsDetailsLines, rec).write(vals)
rec.invalidate_cache()
rec = self.browse(rec.id)
if 'display_type' in vals and self.filtered(lambda line: line.display_type != vals.get('display_type')):
raise UserError(
_("You cannot change the type of a sale quote line. Instead you should delete the current line and create a new line of the proper type."))
if 'benefit_id' in vals:
new_benefit = rec.benefit_id.name or 'None'
sponsorship = self.env['takaful.sponsorship'].browse(
self.env.context.get('active_id'))
if sponsorship and len(sponsorship) == 1:
message = _("Benefit changed from <b>%s</b> to <b>%s</b> in a donation item.") % (
old_benefit or 'None', new_benefit)
sponsorship.message_post(body=message)
if 'benefit_ids' in vals:
new_benefits = rec.benefit_ids.mapped('name')
new_benefits_set = set(new_benefits)
added = new_benefits_set - old_benefits_set
removed = old_benefits_set - new_benefits_set
changes = []
if added:
changes.append(_("Added: <b>%s</b>") % ', '.join(added))
if removed:
changes.append(_("Removed: <b>%s</b>") % ', '.join(removed))
if changes:
# Find sponsorship
sponsorship = rec.sponsorship_id or self.env['takaful.sponsorship'].browse(
self.env.context.get('active_id'))
if sponsorship and len(sponsorship) == 1:
message = _("Benefit IDs changed in a donation item:<br/>%s") % "<br/>".join(changes)
sponsorship.message_post(body=message)
return res
def action_view_replacement_wizard(self):
self.ensure_one()
wizard = self.env['replacement.wiz'].create({
'replacement_line_ids':[(0, 0, {
'sponsorship_type': self.sponsorship_type,
'from_benefit_id': self.benefit_id.id if self.sponsorship_type == 'person' else False,
'from_benefit_ids': [(6, 0, self.benefit_ids.ids)] if self.sponsorship_type == 'group' else False
})]
})
return {
'name': 'Replacement Wizard',
'type': 'ir.actions.act_window',
'res_model': 'replacement.wiz',
'view_mode': 'form',
'res_id': wizard.id,
'target': 'new',
}
class SponsorshipSchedulingLine(models.Model):
_name = 'sponsorship.scheduling.line'
@ -2320,9 +1776,33 @@ class SponsorshipSchedulingLine(models.Model):
record.cancel_refund = hours_passed >= max_hours
else:
record.cancel_refund = False
def action_register_payment(self):
invoice_id = self.env['account.move'].sudo().search([('takaful_sponsorship_id', 'in', [self.sponsorship_id.id])], limit=1)
if not invoice_id:
raise ValidationError(_("No invoice found for this sponsorship."))
# payment_method_line = self.env['account.payment.method.line'].search([
# ('journal_id', '=', self.payment_method_id.journal_id.id),
# ('payment_type', '=', 'inbound')
# ], limit=1)
return {
'name': _('Register Payment'),
'res_model': 'account.payment.register',
'view_mode': 'form',
'context': {
'active_model': 'account.move',
'active_ids': invoice_id.ids,
'dont_redirect_to_payments': True,
'sponsorship_line_ids': self.donation_detail_linked_id.ids,
'default_amount': self.amount,
'default_journal_id': self.donation_detail_linked_id.payment_method_id.journal_id.id,
'schedule_line_payment': True,
'schedule_line_id': self.id,
},
'target': 'new',
'type': 'ir.actions.act_window',
}
def pay_sponsorship_scheduling(self):
"""
@ -2493,9 +1973,6 @@ class RefundDetailsLines(models.TransientModel):
invoiced = fields.Boolean(copy=False, readonly=True)
class PaymentDetailsLines(models.Model):
_name = "payment.details.lines"
@ -2540,8 +2017,6 @@ class PaymentDetailsLines(models.Model):
payment_method_id = fields.Many2one('takaful.payment.method', string="Payment Method")
payment_method = fields.Selection(selection=[("cash", "Cash"),("card", "Card"),("check", "Check"),("credit_card", "Credit Card"),("bank_transfer", "Bank Transfer"),("direct_debit", "Direct Debit")], related="payment_method_id.payment_method")
def action_send_whatsapp(self):
notification = self.env['takaful.notification'].sudo().search(
[('notification_type', '=', 'partial_payment'), ('message_type_ids.tool_type', '=', 'app')],

View File

@ -65,7 +65,7 @@ class TakafulSponsor(models.Model):
('verified', 'Verified')
], string='State',default='draft', tracking=True)
preferred_communication = fields.Many2one('preferred.communication', string="Preferred Communication", required=True)
preferred_communication = fields.Many2one('preferred.communication', string="Preferred Communication")
serial_code = fields.Char(string="Serial Code", readonly=True, copy=False)
responsible_user_ids = fields.Many2many(
@ -206,15 +206,14 @@ class TakafulSponsor(models.Model):
# @api.multi
def unlink(self):
sponsorships = self.env['takaful.sponsorship'].search([('sponsor_id', 'in', self.ids)]).sponsor_id
for record in self:
if record.active:
if record.user_id:
record.user_id.sudo().write({
"active": False
})
record.active = False
else:
raise UserError(_('Sponsor is already inactive'))
if record in sponsorships:
raise UserError(_('Sponsor %s is associated with sponsorships') % record.name)
self.user_id.sudo().write({
"active": False
})
return super(TakafulSponsor, self).unlink()
# @api.multi
def on_activate_sponsor_multi(self):
@ -277,7 +276,7 @@ class TakafulSponsor(models.Model):
user = self.env['res.users'].sudo().with_context(no_reset_password=False).create({
'name': res.name,
# 'user_type': 'company',
'login': res.id_number if not res.email else res.email,
'login': res.mobile,
'phone': res.mobile,
'mobile': res.mobile,
'partner_id': res.partner_id.id,
@ -294,7 +293,7 @@ class TakafulSponsor(models.Model):
'middle_name': res.middle_name,
'family_name': res.family_name,
# 'user_type': 'person',
'login': res.id_number if not res.email else res.email,
'login': res.mobile,
'phone': res.mobile,
'mobile': res.mobile,
'partner_id': res.partner_id.id,
@ -309,10 +308,10 @@ class TakafulSponsor(models.Model):
# in odoo relation field accept a list of commands
# command 4 means add the id in the second position must be an integer
# ref return an object so we return the id
( 4, self.env.ref('odex_takaful.takaful_group_user_sponsor').id),
]
(4, self.env.ref('odex_takaful.takaful_group_user_sponsor').id),
(3, self.env.ref('base.group_user', False).id),
(4, self.env.ref('base.group_portal', False).id)]
})
res.user_id = user.id
return res

View File

@ -112,6 +112,8 @@ class SponsorshipPayment(models.Model):
rec.amount = rec.month_amount * rec.payment_month_number
def action_pay(self):
# Check for already sponsored beneficiaries only for sponsorship records
count = 0
invoices = []
if self.sponsorship_id.state in ['confirmed','wait_pay','progress' , 'to_cancel']:
@ -185,6 +187,7 @@ class SponsorshipPayment(models.Model):
).create(payment_register_vals)
payment_register.action_create_payments()
# @api.multi
def open_account_invoice_action(self):

View File

@ -220,6 +220,42 @@
<field name="perm_unlink" eval="1"/>
</record>
<!-- Groups for Department-based Access Control -->
<record id="group_grant_benefit_department_access" model="res.groups">
<field name="name">Grant Benefit - Department Access</field>
<field name="category_id" ref="module_category_kufula"/>
<field name="comment">Limits grant.benefit records to user's department</field>
</record>
<record id="group_family_member_department_access" model="res.groups">
<field name="name">Family Member - Department Access</field>
<field name="category_id" ref="module_category_kufula"/>
<field name="comment">Limits family.member records to user's department</field>
</record>
<!-- Record Rules for Department-based Access Control -->
<record id="grant_benefit_department_rule" model="ir.rule">
<field name="name">Grant Benefit - Department Based Access</field>
<field name="model_id" ref="odex_benefit.model_grant_benefit"/>
<field name="groups" eval="[(4, ref('group_grant_benefit_department_access'))]"/>
<field name="domain_force">[('branch_custom_id.branch', 'child_of', user.employee_id.department_id.id)]</field>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<record id="family_member_department_rule" model="ir.rule">
<field name="name">Family Member - Department Based Access</field>
<field name="model_id" ref="odex_benefit.model_family_member"/>
<field name="groups" eval="[(4, ref('group_family_member_department_access'))]"/>
<field name="domain_force">[('benefit_id.branch_custom_id.branch', 'child_of', user.employee_id.department_id.id)]</field>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
</data>
</odoo>

View File

@ -11,7 +11,7 @@
<field name="is_refund_sponsorship" invisible ="1"/>
</xpath>
<xpath expr="//field[@name='amount']" position="attributes">
<attribute name="attrs">{'readonly':[('is_refund_sponsorship','=',True)]}</attribute>
<attribute name="readonly">context.get('sponsorship_line_ids')</attribute>
</xpath>
<xpath expr="//field[@name='journal_id']" position="attributes">
<attribute name="attrs">{'readonly':[('is_refund_sponsorship','=',True)]}</attribute>

View File

@ -1,14 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="donations_details_lines_view_search" model="ir.ui.view">
<field name="name">donations.details.lines.view.search</field>
<field name="model">donations.details.lines</field>
<field name="arch" type="xml">
<search string="Search Donation Details Lines">
<field name="sequence_no"/>
<field name="sponsor_id"/>
<field name="benefit_id"/>
<field name="product_template_id"/>
<field name="branch_custom_id"/>
<separator/>
<!-- State Filters -->
<filter string="To Pay" name="filter_draft" domain="[('state', '=', 'draft')]"/>
<filter string="Waiting" name="filter_waiting" domain="[('state', '=', 'waiting')]"/>
<filter string="Active" name="filter_active" domain="[('state', '=', 'active')]"/>
<filter string="Closed" name="filter_closed" domain="[('state', '=', 'closed')]"/>
<filter string="Extended" name="filter_extended" domain="[('state', '=', 'extended')]"/>
<separator/>
<!-- Record Type Filters -->
<filter string="Sponsorship" name="filter_sponsorship" domain="[('donation_type', '=', 'sponsorship')]"/>
<filter string="Donation" name="filter_donation" domain="[('donation_type', '=', 'donation')]"/>
<filter string="Waqf" name="filter_waqf" domain="[('donation_type', '=', 'waqf')]"/>
<separator/>
<!-- Sponsorship Duration Filters -->
<filter string="Permanent" name="filter_permanent" domain="[('sponsorship_duration', '=', 'permanent')]"/>
<filter string="Temporary" name="filter_temporary" domain="[('sponsorship_duration', '=', 'temporary')]"/>
<separator/>
<!-- Donation Mechanism Filters -->
<filter string="With Conditions" name="filter_with_conditions" domain="[('donation_mechanism', '=', 'with_conditions')]"/>
<filter string="Without Conditions" name="filter_without_conditions" domain="[('donation_mechanism', '=', 'without_conditions')]"/>
<separator/>
<filter string="Without Beneficiary" name="filter_no_benefit" domain="[('benefit_id', '=', False), ('donation_type', '=', 'sponsorship')]"/>
<filter string="With Beneficiary" name="filter_with_benefit" domain="[('benefit_id', '!=', False), ('donation_type', '=', 'sponsorship')]"/>
<separator/>
<group expand="0" string="Group By">
<filter string="State" name="group_state" context="{'group_by': 'state'}"/>
<filter string="Donation Type" name="group_donation_type" context="{'group_by': 'donation_type'}"/>
<filter string="Sponsorship Duration" name="group_duration" context="{'group_by': 'sponsorship_duration'}"/>
<filter string="Donation Mechanism" name="group_mechanism" context="{'group_by': 'donation_mechanism'}"/>
<filter string="Sponsor" name="group_sponsor" context="{'group_by': 'sponsor_id'}"/>
<filter string="Branch" name="group_branch" context="{'group_by': 'branch_custom_id'}"/>
<filter string="Product" name="group_product" context="{'group_by': 'product_template_id'}"/>
<filter string="Creation Date" name="group_creation_date" context="{'group_by': 'create_date'}"/>
</group>
<separator/>
<searchpanel>
<field name="state" enable_counters="1"/>
<field name="record_type" enable_counters="1"/>
<field name="sponsorship_duration" enable_counters="1"/>
<field name="donation_mechanism" enable_counters="1"/>
</searchpanel>
</search>
</field>
</record>
<record id="donations_details_lines_view_tree" model="ir.ui.view">
<field name="name">donations.details.lines.view.tree</field>
<field name="model">donations.details.lines</field>
<field name="arch" type="xml">
<tree>
<tree default_order="create_date asc">
<field name="sponsorship_scheduling_line_ids" invisible="1" />
<field name="payment_method" invisible="1" />
<field name="sequence_no" />
<field name="sponsor_id" />
<field name="sponsor_phone" />
<field name="donation_type" optional="show"/>
<field name="sponsorship_duration" optional="hide"/>
<field name="donation_mechanism" optional="hide"/>
<field name="payment_method_id" />
<field name="product_template_id" />
<field name="start_date" />
@ -19,11 +79,20 @@
<field name="benefit_family_code" optional="hide" />
<field name="benefit_id" optional="hide" />
<field name="sponsorship_creation_date" />
<field name="create_date" optional="hide"/>
<field name="state" widget="badge"
decoration-muted="state == 'draft'"
decoration-warning="state == 'waiting'"
decoration-success="state == 'active'"
decoration-info="state in ['closed', 'extended']" />
decoration-success="state in ['active', 'paid']"
decoration-danger="state == 'closed'"
decoration-info="state == 'extended'" />
<button name="action_view_scheduling_lines"
string="View Scheduling Lines"
type="object"
attrs="{'invisible': ['|', ('payment_method', '!=', 'direct_debit'), ('sponsorship_scheduling_line_ids', '=', [])]}"
class="btn-secondary"
icon="fa-calendar" />
</tree>
</field>
</record>
@ -34,15 +103,21 @@
<field name="arch" type="xml">
<form string="Donation Details">
<header>
<button name="action_register_payment"
string="Register Payment"
type="object"
class="btn-primary"
attrs="{'invisible': ['|', '|', ('payment_method', '=', 'direct_debit'), ('is_paid', '=', True), ('parent_state', '!=', 'confirmed')]}" />
<button string="Compute Sponsorships"
name="compute_sponsorships_lines"
type="object"
class="oe_highlight"
attrs="{'invisible': ['|', ('sponsorships_computed', '=', True), ('payment_option', '!=', 'month')]}" />
attrs="{'invisible': ['|', ('sponsorships_computed', '=', True), ('payment_method', '!=', 'direct_debit')]}" />
<button string="Orphan Replacement"
name="action_view_replacement_wizard"
type="object"
class="btn-primary"
attrs="{'invisible': ['|', '&amp;',('state', 'not in', ['active', 'paid']), ('record_type', '=', 'donation'), ('donation_mechanism', '=', 'without_conditions')]}"
groups="odex_takaful.group_orphan_replacement" />
<field name="state" widget="statusbar"
statusbar_visible="draft,waiting,active,closed,extended" />
@ -73,6 +148,8 @@
<field name="sponsor_phone" />
<field name="branch_custom_id" />
<field name="sponsorship_creation_date" />
<field name="parent_state" invisible="1" />
<field name="is_paid" invisible="1" />
</group>
</group>
@ -135,7 +212,7 @@
<field name="benefit_type"
attrs="{'invisible': [('donation_mechanism','!=','with_conditions')], 'required': [('donation_mechanism','=','with_conditions')]}" />
<field name="benefit_id"
attrs="{'invisible': ['|', ('sponsorship_type','!=','person'), ('donation_mechanism','!=','with_conditions')], 'required': [('members_domain_ids', '!=', []), ('sponsorship_type','=','person'), ('donation_mechanism','=','with_conditions')]}"
attrs="{'invisible': ['|', ('sponsorship_type','!=','person'), ('donation_mechanism','!=','with_conditions')], 'required': [('members_domain_ids', '!=', []), ('sponsorship_type','=','person'), ('donation_mechanism','=','with_conditions'), ('state', 'in', ['active', 'closed', 'extended'])]}"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}" />
<field name="family_id"
attrs="{'invisible': ['&amp;', ('sponsorship_type','=','group'), ('record_type','=','sponsorship')],
@ -178,7 +255,7 @@
</page>
<page string="Sponsorship Scheduling"
attrs="{'invisible': [('sponsorship_scheduling_line_ids', '=', False)]}">
attrs="{'invisible': ['|', ('sponsorship_scheduling_line_ids', '=', False), ('payment_method', '!=', 'direct_debit')]}">
<field name="sponsorship_scheduling_line_ids" mode="tree">
<tree create="false" delete="false" edit="false">
<field name="sequence_no" />
@ -197,7 +274,7 @@
<!-- To pay sponsorship scheduling line -->
<button string="Pay" name="pay_sponsorship_scheduling"
type="object" class="oe_highlight"
attrs="{'invisible': ['|', ('status', '!=', 'unpaid'), ('sponsorship_state', 'not in', ['confirmed', 'wait_pay','partial_refund'])]}" />
attrs="{'invisible': ['|', '|', ('payment_method', '=', 'direct_debit'), ('status', '!=', 'unpaid'), ('sponsorship_state', 'not in', ['confirmed', 'wait_pay','partial_refund'])]}" />
<!-- To approve refund for donation lines -->
<button string="Approve Refund" name="approve_refund"
@ -233,6 +310,8 @@
<field name="name">Donations Details Lines</field>
<field name="res_model">donations.details.lines</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="donations_details_lines_view_search"/>
<field name="context">{'create': False, 'delete': False, 'search_default_filter_waiting': 1}</field>
</record>
</odoo>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Tree view for product.template with sponsorship button -->
<record id="view_product_template_tree_sponsorship" model="ir.ui.view">
<field name="name">product.template.tree.sponsorship</field>
<field name="model">product.template</field>
<field name="arch" type="xml">
<tree string="Products">
<header>
<button name="link_to_sponsorship"
type="object"
string="Link to Sponsorship"
class="btn-primary"
icon="fa-link"/>
</header>
<field name="name"/>
<field name="donation_category"/>
<field name="list_price" string="Price"/>
</tree>
</field>
</record>
</odoo>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Tree View for Sponsorship Scheduling Line -->
<record id="view_sponsorship_scheduling_line_tree" model="ir.ui.view">
<field name="name">sponsorship.scheduling.line.tree</field>
<field name="model">sponsorship.scheduling.line</field>
<field name="arch" type="xml">
<tree string="Sponsorship Scheduling Lines" decoration-success="status == 'paid'"
decoration-info="status == 'unpaid'" decoration-danger="status == 'fully_refund'"
decoration-warning="status in ('approve_refund', 'under_refund', 'partial_refund')">
<field name="sequence_no"/>
<field name="sponsorship_id"/>
<field name="sponsorship_state"/>
<field name="donation_detail_linked_id"/>
<field name="beneficiary_id"/>
<field name="month_year"/>
<field name="amount" sum="Total Amount"/>
<field name="refunded_amount" sum="Total Refunded"/>
<field name="payment_method"/>
<field name="status"/>
<button name="action_register_payment"
string="Register Payment"
type="object"
class="btn-primary"
attrs="{'invisible': [('status', '!=', 'unpaid')]}" />
</tree>
</field>
</record>
<!-- Search View for Sponsorship Scheduling Line -->
<record id="view_sponsorship_scheduling_line_search" model="ir.ui.view">
<field name="name">sponsorship.scheduling.line.search</field>
<field name="model">sponsorship.scheduling.line</field>
<field name="arch" type="xml">
<search string="Search Sponsorship Scheduling Lines">
<field name="sequence_no"/>
<field name="sponsorship_id"/>
<field name="beneficiary_id"/>
<field name="month_year"/>
<field name="donation_detail_linked_id"/>
<separator/>
<filter string="Unpaid" name="unpaid" domain="[('status', '=', 'unpaid')]"/>
<filter string="Paid" name="paid" domain="[('status', '=', 'paid')]"/>
<filter string="Approve Refund" name="approve_refund" domain="[('status', '=', 'approve_refund')]"/>
<filter string="Under Refund" name="under_refund" domain="[('status', '=', 'under_refund')]"/>
<filter string="Partial Refund" name="partial_refund" domain="[('status', '=', 'partial_refund')]"/>
<filter string="Fully Refund" name="fully_refund" domain="[('status', '=', 'fully_refund')]"/>
<separator/>
<filter string="Cash" name="cash" domain="[('payment_method', '=', 'cash')]"/>
<filter string="Card" name="card" domain="[('payment_method', '=', 'card')]"/>
<filter string="Bank Transfer" name="bank_transfer" domain="[('payment_method', '=', 'bank_transfer')]"/>
<filter string="Direct Debit" name="direct_debit" domain="[('payment_method', '=', 'direct_debit')]"/>
<separator/>
<group expand="0" string="Group By">
<filter string="Sponsorship" name="group_sponsorship" context="{'group_by': 'sponsorship_id'}"/>
<filter string="Beneficiary" name="group_beneficiary" context="{'group_by': 'beneficiary_id'}"/>
<filter string="Status" name="group_status" context="{'group_by': 'status'}"/>
<filter string="Payment Method" name="group_payment_method" context="{'group_by': 'payment_method'}"/>
<filter string="Month/Year" name="group_month_year" context="{'group_by': 'month_year'}"/>
<filter string="Sponsorship State" name="group_sponsorship_state" context="{'group_by': 'sponsorship_state'}"/>
</group>
</search>
</field>
</record>
<!-- Action for Sponsorship Scheduling Line -->
<record id="action_sponsorship_scheduling_line" model="ir.actions.act_window">
<field name="name">Sponsorship Scheduling Lines</field>
<field name="res_model">sponsorship.scheduling.line</field>
<field name="view_mode">tree</field>
<field name="search_view_id" ref="view_sponsorship_scheduling_line_search"/>
<field name="context">{'create': False}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new Sponsorship Scheduling Line
</p>
<p>
Track and manage sponsorship payment schedules for beneficiaries.
</p>
</field>
</record>
</odoo>

File diff suppressed because it is too large Load Diff

View File

@ -123,7 +123,7 @@
<field name="city_id"/>
<field name="branch_custom_id"/>
<field name="district_id" invisible="1"/>
<field name="preferred_communication"/>
<field name="preferred_communication" required="1"/>
<field name="responsible_user_ids" widget="many2many_tags" groups="odex_takaful.sponsorship_system_manager_group"/>
<!-- <field name="journal_id"/> -->
<!-- <label for="account_number" string="Bank Account"/>