from odoo import fields, models, api, _ from odoo.exceptions import UserError, ValidationError from datetime import date, datetime, timedelta from dateutil.relativedelta import relativedelta from odoo.tools import html_escape, html2plaintext class ServiceRequest(models.Model): _name = 'service.request' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = "date desc" name = fields.Char(string='Reference', required=True, copy=False, readonly=True, index=True, default=lambda self: _('New')) benefit_type = fields.Selection(string='Benefit Type', selection=[('family', 'Family'), ('member', 'Member')]) date = fields.Datetime(string='Request Date', default=fields.Datetime.now) marriage_contract_date = fields.Date(string="Marriage Contract Date") family_id = fields.Many2one('grant.benefit', string='Family', domain="['|',('state','=','second_approve'),'&',('state','in',['waiting_approve','first_approve']),('action_type','=','suspended')]") researcher_id = fields.Many2one("committees.line", string="Researcher", related="family_id.researcher_id", store=True) family_category = fields.Many2one('benefit.category', string='Family Category', related='family_id.benefit_category_id', store=True) family_code = fields.Char(string='Family Code', related='family_id.code', store=True) benefit_member_count = fields.Integer(string="Benefit Member count", related='family_id.benefit_member_count',store=True) branch_custom_id = fields.Many2one('branch.settings', string="Branch", related='family_id.branch_custom_id', store=True) member_id = fields.Many2one('family.member', string='Member') member_domain_ids = fields.Many2many(comodel_name='family.member', compute='_compute_member_domain_and_count', string="Eligible Members", store=True) 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="[('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') requested_service_amount = fields.Float(string="Requested Service Amount", copy=False,tracking=False) # yearly Estimated Rent Amount estimated_rent_amount = fields.Float(string="Estimated Rent Amount", compute="_get_estimated_rent_amount", store=True) # The value of payment by payment method(yearly-half-quartarly) estimated_rent_amount_payment = fields.Float(string="Estimated Rent Amount Payment", compute="_get_estimated_rent_amount_payment", store=True) service_type = fields.Selection([('rent', 'Rent')], string='Service Type', related='service_cat.service_type') max_limit_period = fields.Selection(string='Maximum Limit Period', related='service_cat.max_limit_period') payment_method = fields.Selection([ ('none', 'None'), ('payment_order', 'Payment Order'), ('invoice', 'Invoice'), ], string='Payment Method', related='service_cat.payment_method', store=True, default="payment_order") # is_alternative_housing = fields.Boolean(string='Is Alternative Housing?') rent_contract_number = fields.Char(string="Rent Contract Number", compute='_compute_rent_details', store=True) rent_start_date = fields.Date(string='Rent Start Date', compute='_compute_rent_details', store=True) rent_end_date = fields.Date(string='Rent End Date', compute='_compute_rent_details', store=True) rent_amount = fields.Float(string='Rent Amount', compute='_compute_rent_details', store=True) rent_amount_payment = fields.Float(string='Rent Amount Payment', compute='_get_rent_amount_payment', store=True) payment_type = fields.Selection( [ ('1', 'Yearly'), ('2', 'Half-yearly'), ('4', 'Quarterly'), ('12', 'Monthly') ], string='Payment Type', compute='_compute_rent_details', store=True ) rent_attachment = fields.Many2many('ir.attachment', 'rel_rent_attachment_service_request', 'service_request_id', 'attachment_id', string='Rent Attachment', compute='_compute_rent_details', store=True) rent_payment_date = fields.Date(string='Rent Payment Date') rent_payment_date_exception = fields.Boolean(string='Rent Payment Date Exception?') start = fields.Date(string="Start Date") end = fields.Date(string='End Date') added_amount_if_mother_dead = fields.Float(string="Added Amount (If mother dead)", compute="_get_added_amount_if_mother_dead", store=True) attachment_lines = fields.One2many( 'service.attachments.settings', 'service_request_id', compute='_compute_attachment_lines', readonly=False, copy=False, store=True ) account_id = fields.Many2one( 'account.account', string='Expenses Account', compute="_compute_account_id" ) device_account_id = fields.Many2one('account.account', string='Expenses Account', related='device_id.account_id') accountant_id = fields.Many2one('res.users', string='Accountant', related='service_cat.accountant_id', store=True) service_producer_id = fields.Many2one('res.partner', string='Service Producer', ) is_service_producer = fields.Boolean(string='Is Service Producer?', related='service_cat.is_service_producer') # maintenance_items_id = fields.Many2one('home.maintenance.lines', string="Maintenance Items") maintenance_items_ids = fields.One2many('home.maintenance.items', 'service_request_id', string="Maintenance Items", ) payment_order_ids = fields.Many2many(comodel_name='payment.orders', relation='service_request_payment_order_rel', column1='service_request_id', column2='payment_order_id', string='Payment Orders', copy=False, ) payment_order_id = fields.Many2one('payment.orders', string='Payment Order', copy=False) payment_order_count = fields.Integer(compute='_compute_payment_order', string='Number of Payment Orders') is_payment_order_done = fields.Boolean(string='Is Payment Order Done?') aid_amount = fields.Float(string='Aid Amount', compute='_get_aid_amount') # Fields for alternative house providing_alternative_housing_based_rent = fields.Boolean(string='Providing alternative housing based on rent') rent_for_alternative_housing = fields.Many2one('services.settings', compute='_get_rent_for_alternative_housing') # Fields for electrical_devices service device_id = fields.Many2one('electrical.devices', string='Device', domain="[('min_count_member','<=',benefit_member_count),('max_count_member','>=',benefit_member_count)]") vendor_bill = fields.Many2one('account.move', copy=False) requested_quantity = fields.Integer(string='Requested Quantity') exception_or_steal = fields.Boolean(string='Exception Or Steal?') exception_or_steal_attach = fields.Many2many('ir.attachment', 'rel_exception_or_steal_attachment_service_request', 'exception_or_steal_id', 'attachment_id', string='Exception or steal Attachment') # Home furnishing Exception home_furnishing_exception = fields.Boolean(string='Exception(Fire Or Steal or Natural disaster)') furnishing_items_ids = fields.One2many('home.furnishing.items', 'service_request_id', string="Furnishing Items") # Marriage member_age = fields.Integer(string="Member Age", related="member_id.age") member_payroll = fields.Float(string="Member Payroll", related="member_id.member_income") has_marriage_course = fields.Selection(selection=[ ('yes', 'Yes'), ('no', 'No'), ], string='Has Marriage Course') service_benefit_count = fields.Integer(string='Service Benefit Count', compute="_compute_member_domain_and_count") # Buy home amount_for_buy_home_for_member_count = fields.Float(string="Amount For Buy Home for member count") home_age = fields.Integer(string='Home Age') required_attach = fields.Boolean(string='Required Attach', related='service_cat.required_attach') state = fields.Selection(selection=[ ('draft', 'Draft'), ('waiting_family', 'Waiting Family'), ('researcher', 'Researcher'), ('waiting_approve', 'Waiting for Operation Manager'), ('first_approve', 'Waiting for Branch Manager'), ('family_services_manager', 'Waiting Family Services Manager'), ('legal_department', 'Waiting Legal Department'), ('projects_department', 'Waiting Projects Department'), ('gm_assistant', 'Waiting Assistant General Manager'), ('accounting_approve', 'Accounting Approve'), ('return_to_bank', 'Return to Bank'), ('approval_of_beneficiary_services', 'Approval of beneficiary services'), ('send_request_to_supplier', 'Send Request To Supplier'), ('family_received_device', 'Family Received Device'), ('refused', 'Refused') ], string='state', default='draft', tracking=True, group_expand='_expand_states') # dynamic_state = fields.Selection(selection=[],string="State",default='draft',tracking=True,) state_a = fields.Selection(related='state', tracking=False) state_b = fields.Selection(related='state', tracking=False) refuse_reason_id = fields.Many2one('service.refuse.reason', string="Refuse Reason") return_reason = fields.Text(string="Reason for Returning the Request") refuse_reason = fields.Text(string="Reason for Refusal") specialist_note = fields.Text(string="Specialist's Note After Return") exception = fields.Boolean(string='Exception', default=False) exception_attach = fields.Many2many('ir.attachment', 'rel_exception_attachment_service_request', 'service_request_id', 'attachment_id', string='Exception Attachment') service_conditions = fields.Html(related='service_cat.service_conditions', string="Service Conditions") service_approval_date = fields.Datetime(string="Service Approval Date", readonly=True, ) company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.user.company_id) currency_id = fields.Many2one('res.currency', string="Currency", related='company_id.currency_id') 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') car_name = fields.Char(string='Car Name') car_owner_id = fields.Many2one('family.member', domain="[('benefit_id','=',family_id)]", string="Car Owner") car_model_id = fields.Many2one('benefit.vehicle.model', string='Car model') car_price = fields.Float(string='Car Price') car_remaining_amount = fields.Float(string='Remaining Amount', compute='_compute_remaining_amount', store=True, ) show_car_remaining_amount = fields.Boolean(string='Show Car Remaining Amount', compute='_compute_remaining_amount') application_form = fields.Many2many('ir.attachment', 'request_application_form_rel', 'request_id', 'attachment_id', string="Application Form") driving_license = fields.Many2many('ir.attachment', 'request_driving_license_rel', 'request_id', 'attachment_id', string="Driving License") owner_identity = fields.Many2many('ir.attachment', 'request_owner_identity_rel', 'request_id', 'attachment_id', string="Owner Identity") is_driver_family_member = fields.Boolean(string="Is Driver Family Member?", default=True) driver_name = fields.Char(string="Driver Name") seasonal_service_id = fields.Many2one('seasonal.service', string='Seasonal Service', ondelete='cascade') is_seasonal = fields.Boolean(string='Is Seasonal Service?', related='service_cat.is_seasonal_service') is_in_kind = fields.Boolean(string="In Kind", default=False) service_qty = fields.Float(string='Quantity', default=1) payment_order_state = fields.Selection(string='Payment Order State', selection=[ ('none', 'None'), ('waiting', 'Waiting Payment'), ('done', 'Done Payment'), ], copy=False, compute="_compute_payment_order_state", store=True) total_moves = fields.Integer(string="Total Move", compute='_get_total_move_lines') return_reason_id = fields.Many2one("return.reason", string="Return Reason") agree_terms = fields.Boolean(string="I agree to the Terms and Conditions", default=False, ) related_information_html = fields.Html(string="Related Information", compute='_compute_related_information_html', store=True, ) researcher_opinion = fields.Html(string='Specialist Opinion', tracking=True) def action_create_project(self): pass def _check_required_attachments(self): self.ensure_one() current_state = self.state required_docs = self.attachment_lines.filtered(lambda l: l.state == current_state) missing_docs = required_docs.filtered(lambda l: not l.service_attach) if missing_docs: # missing_names = ", ".join(missing_docs.mapped('name')) missing_names = ", ".join([doc.name for doc in missing_docs if doc.name]) raise UserError( _("Cannot proceed with approval. The following required documents for status '%(status)s' are missing: %(missing)s") % { 'status': dict(self._fields['state'].selection).get(current_state, current_state), 'missing': missing_names }) @api.depends('service_cat', 'family_id', 'member_id', 'benefit_type') def _compute_related_information_html(self): for rec in self: if not rec.service_cat: no_service_title = _("No service selected") no_service_desc = _("Please select a service category to view related information.") rec.related_information_html = f"""
📋

{no_service_title}

{no_service_desc}

""" continue family_rows = [] member_rows = [] family_title = _("Family Information") member_title = _("Member Information") for ir_field in rec.service_cat.family_related_fields: row = rec._get_field_display_value(rec.family_id, ir_field) if row: family_rows.append(row) if rec.benefit_type == 'member' and rec.member_id: for ir_field in rec.service_cat.member_related_fields: row = rec._get_field_display_value(rec.member_id, ir_field) if row: member_rows.append(row) family_table = rec._build_info_table(family_title, family_rows) member_table = rec._build_info_table(member_title, member_rows) if member_rows else "" rec.related_information_html = f"""
{family_table} {member_table}
""" @api.onchange('is_driver_family_member') def _onchange_is_driver_family_member(self): for rec in self: if rec.is_driver_family_member: rec.driver_name = False else: rec.car_owner_id = False def _build_info_table(self, title, rows): field_name_label = _("Field Name") value_label = _("Value") no_info_text = _("No information available for this section.") if not rows: return f"""

{title}

{no_info_text}
""" return f"""

{title}

{''.join(rows)}
{field_name_label} {value_label}
""" def _get_field_display_value(self, record, ir_field): if not record or not ir_field: return "" field_name = ir_field.name field = record._fields.get(field_name) if not field: return "" value = record[field_name] label = html_escape(ir_field.field_description or field_name) if value in (False, None, '') or (hasattr(value, '__len__') and len(value) == 0): display = '—' elif field.type == 'boolean': display = f"✓ {_('Yes')}" if value else f"✗ {_('No')}" elif field.type == 'selection': sel_dict = dict(field._description_selection(record.env)) display_val = sel_dict.get(value, value) display = f'{html_escape(str(display_val or "—"))}' elif field.type == 'many2one': display = f'{html_escape(value.name or "—")}' elif field.type == 'many2many': if field.comodel_name == 'ir.attachment': if not value: display = '—' else: links = [] for attach in value: filename = attach.name or _("Unnamed file") url = f"/web/content/{attach.id}?download=true" links.append(f'📎 {html_escape(filename)}') display = '
'.join(links) else: badges = [f'{html_escape(r.name or "-")}' for r in value] display = ' '.join(badges) or '—' elif field.type == 'one2many': items = [ f'
• {html_escape(r.display_name or r.name or "-")}
' for r in value[:20]] more = f'
... {_("and")} {len(value) - 20} {_("more records")}
' if len( value) > 20 else "" display = ''.join(items) + more or '—' elif field.type == 'binary': if value: filename = getattr(record, f"{field_name}_fname", None) or _("Download file") url = f"/web/content/{record._name}/{record.id}/{field_name}?download=true" display = f'📎 {html_escape(filename)}' else: display = '—' else: display = html_escape(str(value)) return f""" {label} {display} """ @api.depends('payment_order_ids') def _compute_payment_order(self): for rec in self: if rec.payment_order_ids: rec.payment_order_count = len(rec.payment_order_ids) else: rec.payment_order_count = 0 @api.depends('payment_order_id', 'payment_order_id.state', 'vendor_bill', 'vendor_bill.state') def _compute_payment_order_state(self): for rec in self: payment_order_state = 'none' if rec.payment_order_id: if rec.payment_order_id.state == "done": payment_order_state = "done" rec.service_approval_date = fields.Datetime.now() if rec.state == 'accounting_approve': rec.state = 'send_request_to_supplier' rec.is_payment_order_done = True else: payment_order_state = "waiting" elif rec.vendor_bill: if rec.vendor_bill.state == "posted": payment_order_state = "done" rec.state = 'send_request_to_supplier' else: payment_order_state = "waiting" rec.payment_order_state = payment_order_state @api.depends('car_price', 'requested_service_amount', 'service_cat') def _compute_remaining_amount(self): for rec in self: if rec.service_cat and rec.service_cat.service_type == 'buy_car': if rec.car_price and rec.requested_service_amount: rec.car_remaining_amount = rec.car_price - rec.requested_service_amount rec.show_car_remaining_amount = rec.car_price > rec.requested_service_amount else: rec.car_remaining_amount = 0.0 rec.show_car_remaining_amount = False else: rec.car_remaining_amount = 0.0 rec.show_car_remaining_amount = False def action_return_bank(self): self.ensure_one() return { 'name': _("Bank Return"), 'type': 'ir.actions.act_window', 'res_model': 'return.reason.wizard', 'view_mode': 'form', 'target': 'new', 'context': { 'default_line_id': self.id, 'default_line_model': 'service.request', } } def action_processed(self): for record in self: record.state = 'accounting_approve' @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') def _compute_is_orphan(self): for rec in self: rec.is_orphan = bool(getattr(rec.family_id.mother_marital_conf, 'is_dead', False)) def _expand_states(self, states, domain, order): return [key for key, val in type(self).state.selection] # TODO: remove this method @api.depends('service_cat') def _compute_account_id(self): for rec in self: rec.account_id = rec.service_cat.account_id @api.depends('service_cat') def _compute_attachment_lines(self): for rec in self: commands = [(5, 0, 0)] if rec.service_cat: for attachment_line in rec.service_cat.attachment_lines: commands.append((0, 0, { 'service_id': False, 'service_request_id': rec.id, 'name': attachment_line.name, 'notes': attachment_line.notes, 'previous_service_attachment_settings_id': attachment_line.id, 'state': attachment_line.state, })) rec.attachment_lines = commands @api.model def create(self, vals): res = super(ServiceRequest, self).create(vals) if not res.name or res.name == _('New'): res.name = self.env['ir.sequence'].sudo().next_by_code('service.request.sequence') or _('New') return res def unlink(self): for request in self: if request.state not in ['draft']: raise UserError(_('You cannot delete this record')) return super(ServiceRequest, self).unlink() @api.depends('family_id', 'member_id', 'benefit_type') def _compute_rent_details(self): for rec in self: contract = False if rec.benefit_type == 'family' and rec.family_id: contract = rec.family_id.current_rent_contract_id elif rec.benefit_type == 'member' and rec.member_id: member_contracts = rec.family_id.rent_contract_ids.filtered( lambda c: c.state == 'active' and c.member_id == rec.member_id and c.landlord_type == 'member' ).sorted('start_date', reverse=True) contract = member_contracts[:1] or False if contract: rec.rent_contract_number = contract.name rec.rent_start_date = contract.start_date rec.rent_end_date = contract.end_date rec.rent_amount = contract.rent_amount rec.payment_type = contract.payment_type rec.rent_attachment = [(6, 0, contract.contract_attachment.ids)] else: rec.rent_contract_number = False rec.rent_start_date = False rec.rent_end_date = False rec.rent_amount = 0.0 rec.payment_type = False rec.rent_attachment = [(5, 0, 0)] def _get_aid_amount(self): for rec in self: if rec.service_type == 'rent': rec.aid_amount = rec.requested_service_amount + rec.added_amount_if_mother_dead else: rec.aid_amount = rec.requested_service_amount def _get_rent_for_alternative_housing(self): for rec in self: if rec.service_cat.service_type == 'alternative_housing': rec.rent_for_alternative_housing = self.env['services.settings'].search([('service_type', '=', 'rent')], limit=1).id else: rec.rent_for_alternative_housing = False @api.depends('family_id', 'service_cat') def _compute_member_domain_and_count(self): for rec in self: if not rec.family_id: rec.member_domain_ids = False rec.service_benefit_count = 1 continue family_members = rec.family_id.member_ids benefit_members = family_members.filtered(lambda m: m.member_status == 'benefit') count = 1 if rec.benefit_type == "family": count_members = benefit_members if rec.service_cat.max_age > 0: count_members = family_members.filtered( lambda m: m.age <= rec.service_cat.max_age ) count = len(count_members) domain_members = family_members if rec.service_cat: sc = rec.service_cat if not sc.allow_non_beneficiary: domain_members = benefit_members if sc.is_this_service_for_student: domain_members = domain_members.filtered( lambda m: ( m.education_status == 'educated' and any(edu.case_study == 'continuous' for edu in m.member_education_status_ids) ) ) if rec.benefit_type == "family": count = len(domain_members) rec.service_benefit_count = count rec.member_domain_ids = domain_members def action_send_to_researcher(self): for rec in self: rec._check_required_attachments() if rec.service_cat and rec.service_cat.service_type == 'home_furnishing': for item in rec.furnishing_items_ids: max_amount = item.home_furnishing_items.max_furnishing_amount or 0.0 if 0 < max_amount < item.furnishing_cost: if not item.price_first_attach: raise ValidationError( _("Cannot send to researcher!\n\n" "Item '%(item)s': Cost (%(cost).2f) exceeds max limit (%(max).2f).\n" "→ First price attachment is mandatory.") % { 'item': item.home_furnishing_items.name, 'cost': item.furnishing_cost, 'max': max_amount } ) if not item.price_second_attach: raise ValidationError( _("Cannot send to researcher!\n\n" "Item '%(item)s': Cost (%(cost).2f) exceeds max limit (%(max).2f).\n" "→ Second price attachment is mandatory.") % { 'item': item.home_furnishing_items.name, 'cost': item.furnishing_cost, 'max': max_amount } ) rec.state = 'researcher' def action_return_to_family(self): for rec in self: rec.state = 'waiting_family' def action_researcher_send_request(self): for rec in self: if not html2plaintext(rec.researcher_opinion or '').strip(): raise ValidationError( _('Please write the specialist opinion before completing the action.') ) if not rec.requested_service_amount or rec.requested_service_amount <= 0: raise UserError(_("Please enter a valid service amount.")) rec._check_required_attachments() rec.state = 'waiting_approve' def action_operations_chief_approve(self): for rec in self: rec._check_required_attachments() rec.state = 'first_approve' def action_branch_manager_approve(self): for rec in self: rec._check_required_attachments() if rec.service_cat.needs_services_head_approval or rec.exception: rec.state = 'family_services_manager' else: rec.state = 'accounting_approve' def action_family_services_manager_approve(self): for rec in self: rec._check_required_attachments() if rec.service_cat.needs_legal_approval: rec.state = 'legal_department' elif rec.service_cat.needs_project_management_approval: rec.action_create_project() rec.state = 'projects_department' elif rec.service_cat.needs_beneficiary_manager_approval or rec.exception: rec.state = 'gm_assistant' else: rec.state = 'accounting_approve' def action_legal_department_approve(self): for rec in self: rec._check_required_attachments() if rec.service_cat.needs_project_management_approval: rec.action_create_project() rec.state = 'projects_department' elif rec.service_cat.needs_beneficiary_manager_approval or rec.exception: rec.state = 'gm_assistant' def action_projects_department_approve(self): for rec in self: rec._check_required_attachments() if rec.service_cat.needs_beneficiary_manager_approval or rec.exception: rec.state = 'gm_assistant' else: rec.state = 'accounting_approve' def action_beneficiary_manager_approve(self): for rec in self: rec._check_required_attachments() rec.state = 'accounting_approve' def action_accounting_approve(self): for rec in self: rec._check_required_attachments() if rec.service_type == 'electrical_devices': rec.state = 'approval_of_beneficiary_services' else: rec.service_approval_date = fields.Datetime.now() rec.state = 'send_request_to_supplier' def action_supplier_approve(self): for rec in self: rec._check_required_attachments() rec.service_approval_date = fields.Datetime.now() rec.state = 'send_request_to_supplier' def action_request_done(self): for rec in self: if rec.service_type == 'buy_car': car_vals = { 'benefit_id': rec.family_id.id, 'name': rec.car_name, 'member_id': rec.car_owner_id.id, 'car_model': rec.car_model_id.id, 'is_driver_family_member': rec.is_driver_family_member, 'driver_name': rec.driver_name, 'purchased_by_association': True, } car = self.env['cars.line'].create(car_vals) if rec.application_form: car.application_form = [(6, 0, rec.application_form.ids)] if rec.driving_license: car.driving_license = [(6, 0, rec.driving_license.ids)] if rec.owner_identity: car.owner_identity = [(6, 0, rec.owner_identity.ids)] rec.family_id.has_car = True rec.state = 'family_received_device' def action_send_request_to_supplier(self): for rec in self: rec.state = 'family_received_device' def action_first_refuse(self): return { 'name': _('Reason for Returning the Request'), 'type': 'ir.actions.act_window', 'res_model': 'reason.for.return.wizard', 'view_mode': 'form', 'target': 'new', } def action_refuse(self): return { 'name': _('Refuse Reason'), 'type': 'ir.actions.act_window', 'res_model': 'service.refuse.reason.wizard', 'view_mode': 'form', 'target': 'new', } @api.onchange('rent_payment_date') def onchange_rent_payment_date(self): today_date = fields.Date.today() for rec in self: if rec.rent_payment_date and not rec.rent_payment_date_exception: month_before_rent_payment_date = rec.rent_payment_date - timedelta(days=30) if today_date > month_before_rent_payment_date: raise UserError(_("You Should request At least a month ago rent payment date")) @api.onchange('furnishing_items_ids') def _onchange_home_furnishing_cost(self): furnishing_cost_sum = 0 for rec in self.furnishing_items_ids: furnishing_cost_sum += rec.furnishing_cost self.requested_service_amount = furnishing_cost_sum @api.onchange('family_id', 'service_cat') def _onchange_member(self): for rec in self: if rec.service_cat: rec.benefit_type = rec.service_cat.benefit_type rec.service_producer_id = rec.service_cat.service_producer_id # rec.payment_method = rec.service_cat.payment_method else: rec.benefit_type = False rec.service_producer_id = False # rec.payment_method = 'payment_order' if not rec.family_id: rec.member_id = False rec.service_cat = False rec.available_service_cats = False @api.onchange('start', 'end', 'rent_start_date', 'rent_end_date', 'payment_type') def _check_date_range(self): for rec in self: if rec.service_type != 'rent': continue months_map = { '1': 12, '2': 6, '4': 3, '12': 1 } if rec.payment_type and rec.start: months_to_add = months_map.get(rec.payment_type) if months_to_add: rec.end = rec.start + relativedelta(months=months_to_add, days=-1) contract = rec.family_id.current_rent_contract_id if rec.benefit_type == 'family' else False if rec.benefit_type == 'member' and rec.member_id: member_contracts = rec.family_id.rent_contract_ids.filtered( lambda c: c.state == 'active' and c.member_id == rec.member_id and c.landlord_type == 'member' ).sorted('start_date', reverse=True) contract = member_contracts[:1] or False if contract: if contract.contract_type == 'fixed': if rec.start and rec.end and rec.rent_start_date and rec.rent_end_date: if not (rec.rent_start_date <= rec.start <= rec.rent_end_date and rec.rent_start_date <= rec.end <= rec.rent_end_date): raise UserError( _("Requested rent period must be fully within the contract period " "because the contract is fixed-term.") ) else: if rec.start and rec.rent_start_date: if rec.start <= rec.rent_start_date: raise UserError( _("Requested start date cannot be before the contract start date.") ) if rec.payment_type and rec.start and rec.end: allowed_months = months_map.get(rec.payment_type) if not allowed_months: continue diff = relativedelta(rec.end, rec.start) months_diff = diff.years * 12 + diff.months + 1 if months_diff != allowed_months: raise UserError(_( "Payment Type requires a period of %s months, but selected period is %s months." % (allowed_months, months_diff) )) @api.onchange( 'requested_service_amount', 'benefit_type', 'date', 'service_cat', 'family_id', 'member_id', 'exception_or_steal', 'home_furnishing_exception', 'has_marriage_course', 'home_age', 'device_id', 'requested_quantity', 'amount_for_buy_home_for_member_count', 'marriage_contract_date', 'start', 'end' ) def onchange_requested_service_amount(self): res = {} Service = self.env['service.request'] today = fields.Date.today() date_before_year = today - timedelta(days=365) for rec in self: if not rec.exception and not rec.exception_attach: family_id = rec.family_id.id service_type = rec.service_cat.service_type allowed = rec.service_cat.allowed_recurrence 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 = ['electrical_devices', 'alternative_housing'] base_domain = [('family_id', '=', family_id), ('service_cat', '=', rec.service_cat.id), ('id', '!=', rec._origin.id), ('state', '!=', 'refused')] if rec.benefit_type == "member": base_domain.append(('member_id', '=', rec.member_id.id)) 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.")) if rec.benefit_member_count < rec.service_cat.min_count_member: raise ValidationError( _("You cannot request this service because you are less than %s") % rec.service_cat.min_count_member) if rec.service_cat.service_type == 'recruiting_driver': if not rec.family_id.has_car: raise ValidationError(_("You cannot request this service because you do not have a car")) # son_members_above_age = rec.family_id.member_ids.filtered( # lambda x: x.relationn.relation_type == 'son' and x.age > 18) # daughter_members_above_age = rec.family_id.member_ids.filtered( # lambda x: x.relationn.relation_type == 'daughter' and x.age > 18) children_above_18_living_with_family = rec.family_id.member_ids.filtered( lambda m: m.relationn.relation_type in ('son', 'daughter') and m.age > 18 and not m.member_location_conf.is_far_from_family ) if children_above_18_living_with_family: raise ValidationError( _("You cannot request this service because there is at least one child over 18 years old living with the family.") ) disable_mother = rec.family_id.member_ids.filtered( lambda x: x.relationn.relation_type == 'mother' and bool(x.disabilities_attachment_ids)) work_mother = rec.family_id.member_ids.filtered( lambda x: x.relationn.relation_type == 'mother' and x.is_work) disable_replacement_mother = rec.family_id.member_ids.filtered( lambda x: x.relationn.relation_type == 'replacement_mother' and bool( x.disabilities_attachment_ids)) work_replacement_mother = rec.family_id.member_ids.filtered( lambda x: x.relationn.relation_type == 'replacement_mother' and x.is_work) # if son_members_above_age or daughter_members_above_age: # raise ValidationError( # _("You cannot request this service because children above 18 years")) if rec.family_id.add_replacement_mother and not disable_replacement_mother and not work_replacement_mother: raise ValidationError( _("You cannot request this service because mother should be worked or has disability")) if not rec.family_id.add_replacement_mother and not disable_mother and not work_mother: raise ValidationError( _("You cannot request this service because mother should be worked or has disability")) if service_type in ['home_maintenance', 'complete_building_house', 'buy_home']: existing_request_restoration = Service.search([ ('family_id', '=', family_id), ('service_cat.service_type', '=', 'home_restoration'), ('id', '!=', rec._origin.id), ('state', '!=', 'refused') ], order='date desc', limit=1) if service_type == 'buy_home': complete_building_requests = Service.search([ ('family_id', '=', family_id), ('service_cat.service_type', '=', 'complete_building_house'), ('id', '!=', rec._origin.id), ('state', '!=', 'refused') ], order='date desc', limit=1) if complete_building_requests and complete_building_requests.date: raise UserError(_( "You cannot request 'buy_home' service because there is an existing 'complete_building_house' service request." )) if service_type == 'complete_building_house': buy_home_requests = Service.search([ ('family_id', '=', family_id), ('service_cat.service_type', '=', 'buy_home'), ('id', '!=', rec._origin.id), ('state', '!=', 'refused') ], order='date desc', limit=1) if buy_home_requests and buy_home_requests.date: raise UserError(_( "You cannot request 'complete_building_house' service because there is an existing 'buy_home' service request." )) if existing_request_restoration and existing_request_restoration.date: if service_type == 'buy_home': raise ValidationError(_( "You cannot request this service together with 'home_restoration' within the same recurrence period." )) else: restoration_date = existing_request_restoration.date.date() next_allowed_restoration_date = ( restoration_date + relativedelta(months=interval) if period == 'months' else restoration_date + relativedelta(years=interval) ) if rec.date.date() < next_allowed_restoration_date: raise ValidationError(_( "You cannot request this service together with 'home_restoration' within the same recurrence period." )) if service_type == 'transportation_insurance': if rec.family_id.has_car: raise ValidationError(_("You cannot request this service because you have a car.")) if service_type == 'buy_home': if rec.service_cat.buy_home_max_total_amount < rec.amount_for_buy_home_for_member_count: raise ValidationError(_( "You can request this service because the total housing amount (%.2f) " "is still under the maximum limit of %.2f." ) % (rec.amount_for_buy_home_for_member_count, rec.service_cat.buy_home_max_total_amount)) if rec.home_age > rec.service_cat.home_age: raise ValidationError( _("You cannot request this service Again Because the home Age More than %s") % rec.service_cat.home_age) if rec.start and rec.end: if rec.start > rec.end: raise ValidationError(_("Start date must be before end date.")) overlap_domain = base_domain + [ ('start', '!=', False), ('end', '!=', False), ('start', '<=', rec.end), ('end', '>=', rec.start) ] overlap_requests = Service.search(overlap_domain) if overlap_requests: raise ValidationError( _("This service request overlaps with an existing requests [%s] for the same service " "between %s and %s.") % (", ".join(overlap_requests.mapped('name')), rec.start, rec.end) ) if allowed: if allowed == 'unlimited': pass elif allowed == 'once': if Service.search_count(base_domain): raise ValidationError(_("You cannot request this service more than once.")) else: if service_type in special_services: if service_type == 'electrical_devices': # rec.service_max_amount = rec.device_id.price_unit if rec.device_id else 0.0 if rec.device_id: rec.service_max_amount = rec.device_id.price_unit * rec.requested_quantity rec.requested_service_amount = rec.service_max_amount else: rec.service_max_amount = 0.0 rec.requested_service_amount = 0.0 if rec.device_id: delta_kwargs = {period: interval} date_before = rec.date - relativedelta(**delta_kwargs) domain = base_domain + [ ('device_id', '=', rec.device_id.id), ('date', '>', date_before) ] existing_requests = Service.search(domain) total_previous_qty = sum(existing_requests.mapped('requested_quantity')) total_qty = total_previous_qty + rec.requested_quantity allowed_line = rec.service_cat.electrical_devices_lines.filtered( lambda l: l.id == rec.device_id.id ) allowed_qty = allowed_line.allowed_quantity if allowed_line else 0 if total_qty > allowed_qty: raise ValidationError(_( "You cannot request this device more than %s times within %s %s." ) % (allowed_qty, interval, period)) else: last_request = Service.search(base_domain, order='date desc', limit=1) if last_request and last_request.date: last_date = ( last_request.date.date() if isinstance(last_request.date, datetime) else last_request.date ) next_allowed_date = ( last_date + relativedelta(months=interval) if period == 'months' else last_date + relativedelta(years=interval) ) if isinstance(next_allowed_date, datetime): next_allowed_date = next_allowed_date.date() if rec.date.date() < next_allowed_date: raise ValidationError(_( "You can only request this service again after %s." ) % next_allowed_date.strftime('%Y-%m-%d')) if max_limit_type and service_type not in special_services: if max_limit_type == 'fixed': rec.service_max_amount = rec.service_cat.max_amount elif max_limit_type == 'category': rec.service_max_amount = rec.service_cat.category_amount_lines and max( rec.service_cat.category_amount_lines.filtered( lambda r: r.benefit_category_id.id == rec.family_category.id).mapped('max_amount'), default=0.0) or 0 elif max_limit_type == 'category_person': rec.service_max_amount = rec.service_cat.bill_lines and max(rec.service_cat.bill_lines.filtered( lambda x: x.benefit_category_id.id == rec.family_category.id and x.min_count_member <= rec.benefit_member_count <= x.max_count_member).mapped( 'max_amount_for_bill'), default=0.0) or 0 elif max_limit_type == 'service': pass elif max_limit_type == 'none': pass elif max_limit_type == 'amount_person': rec.service_max_amount = rec.service_cat.limit_person_line_ids and max( rec.service_cat.limit_person_line_ids.filtered( lambda x: x.min_count_member <= rec.benefit_member_count <= x.max_count_member)).amount or 0 elif max_limit_type == 'region': rec.service_max_amount = rec.estimated_rent_amount / 12 if rec.max_limit_period: period_domain = base_domain.copy() if service_type == 'home_furnishing' and not rec.home_furnishing_exception: period_domain += [('home_furnishing_exception', '=', False)] if rec.max_limit_period == "month": if rec.start and rec.end: start_date = rec.start.date() if isinstance(rec.start, datetime) else rec.start end_date = rec.end.date() if isinstance(rec.end, datetime) else rec.end diff = relativedelta(end_date, start_date) num_months = diff.years * 12 + diff.months if end_date > (start_date + relativedelta(months=rec.service_cat.max_months_limit)): raise ValidationError( _("You cannot request this service for more than %s months.") % rec.service_cat.max_months_limit ) if diff.days > 0: num_months += 1 rec.service_max_amount *= num_months if service_type == 'rent': rec.service_max_amount += rec.added_amount_if_mother_dead rec.requested_service_amount = min(rec.rent_amount_payment, rec.estimated_rent_amount_payment + rec.added_amount_if_mother_dead) elif rec.max_limit_period == "calendar_year": current_date = rec.date.date() if isinstance(rec.date, datetime) else rec.date allowed_years = rec.service_cat.allowed_period or 1 start_year = current_date.year - (allowed_years - 1) end_year = current_date.year year_start = date(start_year, 1, 1) year_end = date(end_year, 12, 31) period_domain += [ ('date', '>=', datetime.combine(year_start, datetime.min.time())), ('date', '<=', datetime.combine(year_end, datetime.max.time())), ] existing_requests = Service.search(period_domain) total_spent = sum(existing_requests.mapped('requested_service_amount')) rec.service_max_amount -= total_spent elif rec.max_limit_period == "year_from_request": current_date = rec.date.date() if isinstance(rec.date, datetime) else rec.date allowed_years = rec.service_cat.allowed_period or 1 period_start_date = current_date - relativedelta(years=allowed_years) period_end_date = current_date period_domain += [ ('date', '>=', datetime.combine(period_start_date, datetime.min.time())), ('date', '<=', datetime.combine(period_end_date, datetime.max.time())), ] existing_requests = Service.search(period_domain) total_spent = sum(existing_requests.mapped('requested_service_amount')) rec.service_max_amount -= total_spent elif rec.max_limit_period == "individual": rec.service_max_amount *= rec.service_benefit_count if rec.service_cat.service_type == 'marriage': if rec.marriage_contract_date and rec.date: request_date = rec.date.date() if isinstance(rec.date, datetime) else rec.date contract_date = rec.marriage_contract_date diff_days = (request_date - contract_date).days if diff_days > 365: raise ValidationError( _("You cannot request this service because the marriage contract date exceeds one year.")) rec.service_max_amount = rec.service_cat.fatherless_member_amount if rec.is_orphan: rec.service_max_amount = rec.service_cat.orphan_member_amount rec.requested_service_amount = rec.service_max_amount if rec.member_age > rec.service_cat.max_age: raise ValidationError(_("Member Age should be less than %s ") % rec.service_cat.max_age) if rec.member_id.is_work and rec.member_payroll > rec.service_cat.member_max_payroll: raise ValidationError( _("Member Payroll should be less than %s ") % rec.service_cat.member_max_payroll) if not rec.is_orphan and rec.requested_service_amount > rec.service_max_amount: raise ValidationError(_("You cannot request more than %s ") % rec.service_max_amount) if rec.is_orphan and rec.requested_service_amount > rec.service_max_amount: raise ValidationError(_("You cannot request more than %s ") % rec.service_max_amount) if rec.has_marriage_course == 'no': raise UserError(_("You Should take a course")) continue if service_type == 'home_furnishing': if rec.home_furnishing_exception: rec.service_max_amount = rec.service_cat.max_furnishing_amount_if_exception if rec.requested_service_amount > rec.service_max_amount and service_type not in special_services and not max_limit_type == 'none': raise ValidationError( _("You cannot request more than %s") % rec.service_max_amount ) if rec.benefit_type == 'family' and rec.service_cat.service_type == 'alternative_housing' and not rec.providing_alternative_housing_based_rent: if rec.requested_service_amount > rec.service_cat.rent_amount_for_alternative_housing: raise UserError( _("You Cannot request amount more than %s") % rec.service_cat.rent_amount_for_alternative_housing) elif rec.rent_period > rec.service_cat.rent_period: raise UserError( _("You Cannot request this service for period more than %s") % rec.service_cat.rent_period) @api.onchange('member_id') def onchange_member_id(self): for rec in self: if rec.member_id and rec.service_type == 'rent' and not rec.member_id.member_location_conf.is_far_from_family: raise UserError(_("You Cannot request Service if you not study inside Saudi Arabia")) @api.depends('family_category') def _compute_available_service_cats(self): for rec in self: domain = [('is_seasonal_service', '=', False), ('service_type', '!=', 'main_service'), ('benefit_category_ids', 'in', [rec.family_category.id])] if rec.family_id: mother_country = rec.family_id.mother_country_id father_country = rec.family_id.father_country_id country_domain = [ '|', ('allowed_country_ids', '=', False), '|', ('allowed_country_ids', 'in', [mother_country.id] if mother_country else []), ('allowed_country_ids', 'in', [father_country.id] if father_country else []) ] allowed_services = self.env['services.settings'].search(country_domain) domain.append(('id', 'in', allowed_services.ids if allowed_services else [])) if rec.family_id.property_type_id: if rec.family_id.property_type_code != 'ownership': domain.append(('service_type', '!=', 'home_restoration')) else: domain.append(('service_type', '!=', 'buy_home')) unavailable_services = rec.family_id.property_type_id.unavailable_service_ids if unavailable_services: domain.append(('id', 'not in', unavailable_services.ids)) rec.available_service_cats = rec.available_service_cats.sudo().search(domain) def action_set_to_draft(self): for rec in self: rec.state = 'draft' def action_accounting_transfer(self): validation_setting = self.env["family.validation.setting"].search([], limit=1) line_ids = [] service_cats = self.mapped('service_cat') if len(service_cats) > 1: cat_names = ", ".join(service_cats.mapped('service_name')) raise UserError(_( "All selected service requests must belong to the same Service Cat.\n\n" "Selected Services Cat:\n%s" ) % cat_names) if service_cats.payment_method == "payment_order": invalid_records = self.filtered( lambda r: r.state != 'accounting_approve' or r.payment_order_state != 'none' or r.payment_order_id ) if invalid_records: names = ", ".join(invalid_records.mapped('name')) raise UserError(_( "The following service requests do not meet the conditions:\n%s\n" "Each request must:\n" "• Be in 'Accounting Approve' state\n" "• Have payment order state = 'None'\n" "• Not be linked to any payment order" ) % names) payment_order = self.env['payment.orders'].create({ 'state': 'draft', 'accountant_id': service_cats.accountant_id.id, 'service_requests_ids': [(6, 0, self.ids)], 'type': 'services', 'payment_order_description': service_cats.id, }) self.write({ 'payment_order_ids': [(4, payment_order.id)], 'payment_order_id': payment_order.id, }) elif service_cats.payment_method == "invoice": missing_producer = self.filtered(lambda r: not r.service_producer_id) if missing_producer: names = ", ".join(missing_producer.mapped('name')) raise UserError(_( "The following service requests do not have a Service Producer defined:\n%s" ) % names) invalid_records = self.filtered( lambda r: r.state != 'accounting_approve' or r.payment_order_state != 'none' or r.vendor_bill ) if invalid_records: names = ", ".join(invalid_records.mapped('name')) raise UserError(_( "The following service requests do not meet the conditions:\n%s\n" "Each request must:\n" "• Be in 'Accounting Approve' state\n" "• Have payment order state = 'None'\n" "• Not be linked to any invoice" ) % names) for rec in self: invoice_line = (0, 0, { 'name': f'{rec.family_id.name}/{rec.device_id.device_name}/{rec.description}/{rec.name}', 'account_id': rec.device_account_id.id, 'analytic_account_id': rec.family_id.branch_family_id.branch.analytic_account_id.id, 'quantity': rec.requested_quantity, 'price_unit': rec.requested_service_amount, 'benefit_family_id': rec.family_id.id, }) line_ids.append(invoice_line) vendor_bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self[0].service_producer_id.id, 'journal_id': validation_setting.journal_id.id, # 'accountant_id': self.accountant_id.id, 'invoice_line_ids': line_ids, }) self.vendor_bill = vendor_bill def _get_total_move_lines(self): for rec in self: if self.service_cat.payment_method == "payment_order": moves = rec.payment_order_ids.mapped('move_id') elif self.service_cat.payment_method == "invoice": moves = self.vendor_bill rec.total_moves = len(moves) def action_open_related_move_records(self): if self.service_cat.payment_method == "payment_order": moves = self.payment_order_ids.mapped('move_id') elif self.service_cat.payment_method == "invoice": moves = self.vendor_bill return { 'name': _('Vendor Bills'), 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'view_mode': 'tree,form', 'domain': [('id', 'in', moves.ids)], } def action_open_payment_orders(self): self.ensure_one() if not self.payment_order_ids: raise UserError(_("No payment orders are linked to this request.")) return { 'name': _('Payment Orders'), 'type': 'ir.actions.act_window', 'res_model': 'payment.orders', 'view_mode': 'tree,form', 'domain': [('id', 'in', self.payment_order_ids.ids)], 'context': {'create': False}, } @api.depends('family_id', 'service_type', 'service_cat') def _get_estimated_rent_amount(self): for rec in self: rec.estimated_rent_amount = 0.0 if not rec.family_id: continue if rec.service_type == 'rent': for item in rec.service_cat.rent_lines: if rec.family_id.benefit_category_id != item.benefit_category_id or rec.family_id.benefit_member_count != item.benefit_count: continue # Determine rent amount based on branch type and property type branch_type = rec.family_id.branch_family_id.branch_type is_shared_rent = ( rec.family_id.property_type_id and rec.family_id.property_type_id.type == 'rent' and rec.family_id.property_type_id.is_shared ) discount = item.discount_rate_shared_housing / 100.0 if is_shared_rent else 1 if branch_type == 'branches': rec.estimated_rent_amount = item.estimated_rent_branches * discount elif branch_type == 'governorates': rec.estimated_rent_amount = item.estimated_rent_governorate * discount if rec.service_type == 'alternative_housing': for item in rec.rent_for_alternative_housing.rent_lines: # Check if benefit category and member count match if rec.family_id.benefit_category_id != item.benefit_category_id or rec.family_id.benefit_member_count != item.benefit_count: continue # Determine rent amount based on branch type and property type branch_type = rec.family_id.branch_custom_id.branch_type is_shared_rent = ( rec.family_id.property_type_id and rec.family_id.property_type_id.type == 'rent' and rec.family_id.property_type_id.is_shared ) if branch_type == 'branches': rec.estimated_rent_amount = item.estimated_rent_branches * ( item.discount_rate_shared_housing if is_shared_rent else 1) elif branch_type == 'governorates': rec.estimated_rent_amount = item.estimated_rent_governorate * ( item.discount_rate_shared_housing if is_shared_rent else 1) @api.depends('estimated_rent_amount', 'payment_type') def _get_estimated_rent_amount_payment(self): for rec in self: rec.estimated_rent_amount_payment = rec.estimated_rent_amount if rec.estimated_rent_amount and rec.payment_type: rec.estimated_rent_amount_payment = rec.estimated_rent_amount / int(rec.payment_type) @api.depends('rent_amount', 'payment_type') def _get_rent_amount_payment(self): for rec in self: if rec.rent_amount and rec.payment_type: rec.rent_amount_payment = rec.rent_amount * 12 / int(rec.payment_type) else: rec.rent_amount_payment = 0.0 @api.depends('family_id', 'service_cat', 'payment_type') def _get_added_amount_if_mother_dead(self): for rec in self: added_amount_if_mother_dead = 0.0 if rec.family_id.mother_marital_conf.is_dead: added_amount_if_mother_dead = rec.service_cat.raise_amount_for_orphan if rec.service_cat.raise_amount_for_orphan and rec.payment_type: added_amount_if_mother_dead = rec.service_cat.raise_amount_for_orphan / int(rec.payment_type) rec.added_amount_if_mother_dead = added_amount_if_mother_dead