465 lines
22 KiB
Python
465 lines
22 KiB
Python
# -*- coding: utf-8 -*-
|
|
##############################################################################
|
|
#
|
|
# Expert (LCT, Life Connection Technology)
|
|
# Copyright (C) 2021-2022 LCT
|
|
#
|
|
##############################################################################
|
|
|
|
import base64
|
|
import json
|
|
from lxml import etree
|
|
import re
|
|
from odoo import models, fields, api, exceptions, _
|
|
from odoo.modules.module import get_module_resource
|
|
|
|
|
|
class Property(models.Model):
|
|
_name = 'internal.property'
|
|
_description = 'Property'
|
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
|
_order = "id desc"
|
|
|
|
# Smart button to count related maintenance records
|
|
maintenance_count = fields.Integer(string="Maintenance Count", compute='_compute_maintenance_count')
|
|
is_unit_count_fixed = fields.Boolean(string="Is Unit Count Fixed in Floor")
|
|
floor_ids= fields.One2many(comodel_name='floor.unit.details', inverse_name='property_id',string='')
|
|
account_analy_id = fields.Many2one('account.analytic.account', string='Analytic Account')
|
|
is_new = fields.Boolean(string="Is New Property?", default=False)
|
|
suitability_for_residence = fields.Selection([
|
|
('residential', 'Residential'),
|
|
('non_residential', 'Non-Residential'),
|
|
], string="Property Suitability for Residence")
|
|
contract_counts = fields.Integer(string='Contracts', compute='count_contracts_number')
|
|
def count_contracts_number(self):
|
|
contract_count = self.env['rental.contract'].search([('property_id', '=', self.id)])
|
|
self.contract_counts = len(contract_count)
|
|
|
|
def get_contract(self):
|
|
contract_id = self.env['rental.contract'].search(
|
|
[('property_id', '=', self.id)])
|
|
form_id = self.env.ref('property_management.rental_contract_form_view').id
|
|
list_id = self.env.ref('property_management.rental_contract_list_view').id
|
|
domain = [('id', 'in', contract_id.ids)]
|
|
return {
|
|
'name': _('Rental Contract'),
|
|
'view_type': 'form',
|
|
'view_mode': 'form',
|
|
'res_model': 'rental.contract',
|
|
'views': [(list_id, 'tree'), (form_id, 'form')],
|
|
'type': 'ir.actions.act_window',
|
|
'target': 'current',
|
|
'domain': domain,
|
|
}
|
|
|
|
|
|
|
|
# @api.model
|
|
# def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
|
# res = super(Property, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar,
|
|
# submenu=submenu)
|
|
# doc = etree.XML(res['arch'])
|
|
# if (view_type == 'form'):
|
|
# for node in doc.xpath("//field"):
|
|
# modifiers = json.loads(node.get("modifiers"))
|
|
# modifiers['readonly'] = [('state', 'in', ['approve'])]
|
|
# node.set("modifiers", json.dumps(modifiers))
|
|
# res['arch'] = etree.tostring(doc, encoding='unicode')
|
|
# return res
|
|
|
|
@api.onchange('floors_count')
|
|
def _onchange_no_of_floor(self):
|
|
if self.floors_count>0:
|
|
# Clear existing lines
|
|
self.floor_ids = [(5, 0, 0)]
|
|
# Create floor records based on the number of floors
|
|
floors = []
|
|
for i in range(1, self.floors_count + 1):
|
|
|
|
floor_name = _('Floor') + ' ' + str(i) # Construct the floor name
|
|
|
|
# Search for the role with the given floor name
|
|
role_record = self.env['property.role'].search([('name', '=', floor_name)], limit=1)
|
|
|
|
if not role_record:
|
|
# If not found, create a new role with the corresponding name
|
|
role_record = self.env['property.role'].create({
|
|
'name': floor_name,
|
|
})
|
|
# Append the record to One2many field
|
|
floors.append((0, 0, {'role_id': role_record.id}))
|
|
self.floor_ids = floors
|
|
|
|
def _compute_maintenance_count(self):
|
|
for record in self:
|
|
record.maintenance_count = self.env['property.management.maintenance'].search_count([
|
|
('property_id', '=', record.id)
|
|
])
|
|
|
|
def action_view_maintenance(self):
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': 'Maintenance',
|
|
'view_mode': 'tree,form',
|
|
'res_model': 'property.management.maintenance',
|
|
'domain': [('property_id', '=', self.id)],
|
|
'context': dict(self.env.context),
|
|
}
|
|
|
|
# Inherit the form view and add the button
|
|
|
|
@api.model
|
|
def _default_image(self):
|
|
image_path = get_module_resource('real_estate', 'static/src/img', 'default_logo.png')
|
|
return base64.b64encode(open(image_path, 'rb').read())
|
|
|
|
active = fields.Boolean(default=True)
|
|
unlock = fields.Boolean(default=True, string="Unlock")
|
|
seq = fields.Char(string="Sequence", index=True)
|
|
name = fields.Char(string="Name")
|
|
state = fields.Selection([('draft', 'Draft'),
|
|
('register', 'Registered'),
|
|
('approve', 'Approved'),
|
|
('reserve', 'Reserved'),
|
|
('rent', 'Rented'),
|
|
('sold', 'Sold')], string="Status", default='draft')
|
|
logo = fields.Binary("Property Logo", default=_default_image, attachment=True,
|
|
help="This field holds the image used as photo for the Property, limited to 1024x1024px.")
|
|
property_type_id = fields.Many2one('internal.property.type', string="Type")
|
|
property_state_id = fields.Many2one('re.property.state', string="Property State")
|
|
ownership_type = fields.Selection(
|
|
[('full', 'Full Ownership'), ('share', 'Share Ownership'), ('inclusion', 'Inclusion')], string='Ownership Type',
|
|
default='full', required=True, tracking=True)
|
|
company_profit = fields.Selection([('percentage', 'Percentage'),
|
|
('number', 'Fixed amount')],
|
|
string='Company Profit')
|
|
company_profit_amount = fields.Float(string='Company Profit Amount')
|
|
|
|
management_type = fields.Selection([('internal_investment', 'Internal Investment'),
|
|
('external_investment', 'External Investment'),
|
|
('include', 'Include')], string="Management Type", default="internal_investment")
|
|
market_type = fields.Selection([('residential', 'Residential'),
|
|
('commercial', 'Commercial'),('residential_commercial', 'Residential and Commercial'),
|
|
('industrial', 'Industrial'),
|
|
('other', 'Other')], string="Market Type", default="commercial")
|
|
other_type = fields.Char(string="Other Type")
|
|
action_type = fields.Selection([('sale', 'Sale')], string="Action Type", default="sale")
|
|
no_units = fields.Integer(string="Number of units", compute="count_unit_number",store=True)
|
|
no_rented_units = fields.Integer(string='Number of rented units', compute='count_unit_number',store=True)
|
|
no_available_units = fields.Integer(string='Number of available units', compute='count_unit_number',store=True)
|
|
no_reserved_units = fields.Integer(string='Number of reserved units', compute='count_unit_number',store=True )
|
|
# no_sold_units = fields.Integer(_('Number Of Sold Units'), compute='count_unit_number')
|
|
city_id = fields.Many2one('re.city', string="City")
|
|
district_id = fields.Many2one('district', string="District")
|
|
street = fields.Char(string="Street Name")
|
|
property_no = fields.Integer(string="Property Number")
|
|
# Building coordinates x and y and it location
|
|
# coordinate_x = fields.Float(string="Coordinate X")
|
|
# coordinate_y = fields.Float(string="Coordinate Y")
|
|
location = fields.Char(string="Location")
|
|
note = fields.Text(string="Note")
|
|
# Building length
|
|
north = fields.Char(string="North Length")
|
|
south = fields.Char(string="South Length")
|
|
east = fields.Char(string="East Length")
|
|
west = fields.Char(string="West Length")
|
|
# Building opening street
|
|
north_street = fields.Char(string="North Street")
|
|
south_street = fields.Char(string="South Street")
|
|
east_street = fields.Char(string="East Street")
|
|
west_street = fields.Char(string="West Street")
|
|
property_face_ids = fields.Many2many('property.faces', string="Property Face")
|
|
user_id = fields.Many2one('res.users', string="Responsible", default=lambda self: self.env.user)
|
|
block_no = fields.Char(string="Block Number")
|
|
licence_no = fields.Char(string="Licence Number")
|
|
plate_no = fields.Char(string="Plate Number")
|
|
plot_no = fields.Char(string="Plot Number")
|
|
planned_no = fields.Char(string="Planned Number")
|
|
planned_id = sketch = fields.Many2one('sketchs.sketchs', string='Planned Name')
|
|
# Stamping information
|
|
stamping_count = fields.Selection(string="Stamping Count", selection=[('1', '1'),
|
|
('2', '2'),
|
|
('3', '3')], default="1")
|
|
stamping = fields.Char(string="Stamping Number")
|
|
stamping_date = fields.Date(string="Stamping Date")
|
|
stamping_attach = fields.Binary("Stamping Attach", attachment=True)
|
|
|
|
stamping_2 = fields.Char(string="Stamping Number")
|
|
stamping_date_2 = fields.Date(string="Stamping Date")
|
|
stamping_attach_2 = fields.Binary("Stamping Attach", attachment=True)
|
|
stamping_state_2 = fields.Selection([('updated', 'Updated'), ('not_updated', 'Not Updated')])
|
|
|
|
stamping_3 = fields.Char(string="Stamping Number")
|
|
stamping_date_3 = fields.Date(string="Stamping Date")
|
|
stamping_attach_3 = fields.Binary("Stamping Attach", attachment=True)
|
|
stamping_state_3 = fields.Selection([('updated', 'Updated'), ('not_updated', 'Not Updated')])
|
|
|
|
# Water Meter information
|
|
water_count = fields.Selection([('1', '1'),
|
|
('2', '2'),
|
|
('3', '3')], string="Meter Count",default="1")
|
|
water_serial = fields.Char(string="Serial Number")
|
|
water_subscription = fields.Char(string="Subscription Number")
|
|
water_account = fields.Char(string="Water Account")
|
|
water_serial2 = fields.Char(string="Serial Number")
|
|
water_subscription2 = fields.Char(string="Subscription Number")
|
|
water_account2 = fields.Char(string="Water Account")
|
|
water_serial3 = fields.Char(string="Serial Number")
|
|
water_subscription3 = fields.Char(string="Subscription Number")
|
|
water_account3 = fields.Char(string="Water Account")
|
|
unit_ids = fields.One2many('re.unit', 'property_id', string="Property Unit")
|
|
property_space = fields.Float(string="Property Space", digits=(16, 2))
|
|
property_unit_space = fields.Float(string="Unit's Space", compute="get_property_space", store=True)
|
|
|
|
uexternal_space = fields.Float(string="Unit External Space", compute="get_unit_info", store=True)
|
|
uspace = fields.Float(string="Unit Total Space", compute="get_unit_info", store=True)
|
|
|
|
rent_price = fields.Float(string="Rent Price", compute="get_rent_price", store=True, digits=(16, 2))
|
|
meter_price = fields.Float(string="Meter Price")
|
|
pur_price = fields.Float(string="Purchase Price")
|
|
pur_meter_price = fields.Float(string="Purchase Price for Meter")
|
|
curr_price = fields.Float(string="Current Purchase Price")
|
|
|
|
property_age = fields.Char(string="Property Age")
|
|
total_price = fields.Float(string="Total Price", compute="get_total_price")
|
|
floors_count = fields.Integer(string="Floors Count")
|
|
appendices = fields.Selection([('yes', 'Yes'),
|
|
('no', 'No')], string="Appendices", default="no")
|
|
separated = fields.Selection([('yes', 'Yes'),
|
|
('no', 'No')], string="Separated", default="no")
|
|
internal_staircase = fields.Selection([('yes', 'Yes'),
|
|
('no', 'No')], string="Internal Staircase", default="no")
|
|
shops_no = fields.Integer(string="Shops Number")
|
|
tree_no = fields.Integer(string="Tree Number")
|
|
apartment_no = fields.Integer(string="Apartment Number")
|
|
buildings_no = fields.Integer(string="Buildings Number")
|
|
well_no = fields.Integer(string="Well Numbers")
|
|
rent_status = fields.Selection([('rented', 'Rented'),
|
|
('not', 'Not Rented')],string="Rent Status", default="not")
|
|
|
|
property_cost = fields.Float(string="Property Cost")
|
|
company_id = fields.Many2one('res.company', string='Company', required=True,
|
|
default=lambda self: self.env.user.company_id)
|
|
# attachment_ids = fields.One2many('ir.attachment', 'property_id', string="Attachment")
|
|
owner_id = fields.Many2one('res.partner', string="Owner")
|
|
faseh = fields.Char('faseh')
|
|
proceedings = fields.Char('Proceedings')
|
|
Areal_decision = fields.Char('Areal Decision')
|
|
offered = fields.Boolean(string="Offered")
|
|
branch_manager_id = fields.Many2one('res.users', string="Branch Manager", default=lambda self: self.env.user)
|
|
marketer_id = fields.Many2one('res.users', string="Marketer")
|
|
unit_counts = fields.Integer('Unit Count', compute="compute_unit_count")
|
|
unit_floor_count = fields.Integer(string="Unit in floor count")
|
|
|
|
room_no = fields.Integer(string="Room Count", compute="get_unit_info", store=True)
|
|
bathroom_no = fields.Integer(string="Bathroom Count", compute="get_unit_info", store=True)
|
|
hall_no = fields.Integer(string="Hall Count", compute="get_unit_info", store=True)
|
|
kitchen_no = fields.Integer(string="kitchen Count", compute="get_unit_info", store=True)
|
|
|
|
|
|
# _sql_constraints = [
|
|
# ('stamping', 'unique(stamping)', _('Stamping must be unique.')),
|
|
# ]
|
|
|
|
|
|
@api.depends('unit_ids', 'unit_ids.room_no', 'unit_ids.bathroom_no', 'unit_ids.hall_no', 'unit_ids.kitchen_no',
|
|
'unit_ids.space', 'unit_ids.external_space')
|
|
def get_unit_info(self):
|
|
for rec in self:
|
|
rec.room_no = sum([unit.room_no for unit in rec.unit_ids])
|
|
rec.bathroom_no = sum([unit.bathroom_no for unit in rec.unit_ids])
|
|
rec.hall_no = sum([unit.hall_no for unit in rec.unit_ids])
|
|
rec.kitchen_no = sum([unit.kitchen_no for unit in rec.unit_ids])
|
|
rec.uexternal_space = sum([unit.external_space for unit in rec.unit_ids])
|
|
uspace = rec.uexternal_space + rec.property_unit_space
|
|
rec.uspace = uspace
|
|
|
|
|
|
@api.depends('property_space', 'meter_price')
|
|
def get_total_price(self):
|
|
for rec in self:
|
|
rec.total_price = rec.meter_price * rec.property_space
|
|
|
|
|
|
def get_unit(self):
|
|
form_id = self.env.ref('real_estate.unit_form_view').id
|
|
domain = [('id', 'in', self.unit_ids.ids)]
|
|
return {
|
|
'name': _('Units'),
|
|
'view_type': 'form',
|
|
'view_mode': 'form',
|
|
'res_model': 're.unit',
|
|
'views': [(False, 'tree'), (form_id, 'form')],
|
|
'type': 'ir.actions.act_window',
|
|
'target': 'current',
|
|
'domain': domain,
|
|
}
|
|
|
|
|
|
def get_attachments(self):
|
|
action = self.env['ir.actions.act_window']._for_xml_id('base.action_attachment')
|
|
action['domain'] = str([('res_model', '=', 'internal.property'),('res_id', 'in', self.ids)])
|
|
action['context'] = "{'default_res_model': '%s','default_res_id': %d}" % (self._name, self.id)
|
|
return action
|
|
|
|
|
|
def compute_unit_count(self):
|
|
self.unit_counts = len(self.unit_ids)
|
|
|
|
|
|
@api.onchange('ownership_type')
|
|
def set_owner(self):
|
|
if self.ownership_type == 'full':
|
|
self.owner = self.company_id.partner_id
|
|
else:
|
|
self.owner = False
|
|
|
|
def get_location(self):
|
|
url = "http://maps.google.com/maps"
|
|
return {
|
|
'type': 'ir.actions.act_url',
|
|
'url': url,
|
|
'target': 'new'
|
|
}
|
|
|
|
|
|
@api.depends('unit_ids', 'unit_ids.space')
|
|
def get_property_space(self):
|
|
"""
|
|
Get total property space from related unit space
|
|
:return:
|
|
"""
|
|
for rec in self:
|
|
rec.property_unit_space = sum([unit.space for unit in rec.unit_ids])
|
|
|
|
|
|
def unlink(self):
|
|
for record in self:
|
|
if record.state != 'draft':
|
|
raise exceptions.ValidationError(_('You cannot delete an approved property.'))
|
|
for unit in record.unit_ids:
|
|
state = dict(unit.fields_get(allfields=['state'])['state']['selection'])[unit.state]
|
|
if unit.state != 'draft':
|
|
raise exceptions.ValidationError(
|
|
_("You cannot delete this property because he have a unit with the following state %s unit "
|
|
"code %s") % state % unit.seq)
|
|
record.unit_ids.unlink()
|
|
return super(Property, self).unlink()
|
|
|
|
def action_register(self):
|
|
"""
|
|
Set Property To register
|
|
:return:
|
|
"""
|
|
self.write({'state': 'register'})
|
|
|
|
def action_approve(self):
|
|
"""
|
|
set Property to approve
|
|
:return:
|
|
"""
|
|
self.write({'state': 'approve'})
|
|
|
|
def action_draft(self):
|
|
"""
|
|
set property state to draft if its not reserved or rented
|
|
:return:
|
|
"""
|
|
state = dict(self.fields_get(allfields=['state'])['state']['selection'])[self.state]
|
|
if self.state not in ('reserve', 'rent'):
|
|
self.write({'state': 'draft'})
|
|
else:
|
|
raise exceptions.ValidationError(
|
|
_("You Cannot set the state of property to draft because it in state %s") % state)
|
|
|
|
@api.model
|
|
def create(self, values):
|
|
"""
|
|
Inherit create operation to set a sequence for a property
|
|
:param values: values while creating a record
|
|
:return: record set
|
|
"""
|
|
values['seq'] = self.env['ir.sequence'].next_by_code('res.property')
|
|
return super(Property, self).create(values)
|
|
|
|
@api.constrains('water_serial', 'water_subscription', 'water_account')
|
|
def fields_check(self):
|
|
"""
|
|
Check if name field contain an invalid value
|
|
:raise exception
|
|
"""
|
|
num_pattern = re.compile(r'\d', re.I | re.M)
|
|
white_space = re.compile(r'^\s')
|
|
if self.water_subscription:
|
|
if not num_pattern.search(self.water_subscription):
|
|
raise exceptions.ValidationError(_("Water subscription field accept numbers or special character only"))
|
|
if white_space.search(self.water_subscription):
|
|
raise exceptions.ValidationError(_("Water subscription (cannot accept white space)"))
|
|
if self.water_account:
|
|
if not num_pattern.search(self.water_account):
|
|
raise exceptions.ValidationError(_("Water account field accept numbers or special character only"))
|
|
if white_space.search(self.water_account):
|
|
raise exceptions.ValidationError(_("Water account (cannot accept white space)"))
|
|
if self.water_serial:
|
|
if not num_pattern.search(self.water_serial):
|
|
raise exceptions.ValidationError(_("Water serial field accept numbers or special character only"))
|
|
if white_space.search(self.water_serial):
|
|
raise exceptions.ValidationError(_("Water serial (cannot accept white space)"))
|
|
|
|
|
|
@api.constrains('meter_price', 'property_space', 'floors_count', 'property_cost')
|
|
def check_number(self):
|
|
"""
|
|
If the number less than zero then raise error
|
|
:return:
|
|
"""
|
|
if self.property_space < 0.0:
|
|
raise exceptions.ValidationError(_("Property space cannot be less than zero"))
|
|
if self.meter_price < 0.0:
|
|
raise exceptions.ValidationError(_("Meter price cannot be less than zero"))
|
|
if self.property_cost < 0.0:
|
|
raise exceptions.ValidationError(_("Property cost cannot be less than zero"))
|
|
if self.floors_count < 0:
|
|
raise exceptions.ValidationError(_("Floors count cannot be less than zero"))
|
|
|
|
@api.depends('unit_ids', 'unit_ids.state')
|
|
def count_unit_number(self):
|
|
for rec in self:
|
|
rec.no_units = len(rec.unit_ids)
|
|
rec.no_available_units = len([unit for unit in rec.unit_ids if unit.state in ['available', 'draft']])
|
|
rec.no_reserved_units = len([unit for unit in rec.unit_ids if unit.state == 'reserved'])
|
|
rec.no_rented_units = len([unit for unit in rec.unit_ids if unit.state == 'rented'])
|
|
|
|
@api.depends('meter_price', 'property_space')
|
|
def get_rent_price(self):
|
|
"""
|
|
Get rent Price Per Meter
|
|
:return: total rent price
|
|
"""
|
|
for record in self:
|
|
record.rent_price = record.meter_price * record.property_space
|
|
|
|
def action_toggle_is_locked(self):
|
|
self.ensure_one()
|
|
if self.unlock:
|
|
self.write({'unlock':False})
|
|
else:
|
|
self.write({'unlock':True})
|
|
class PropertyUnitDetails(models.Model):
|
|
_name = 'floor.unit.details'
|
|
_description = 'Floor Unit Details'
|
|
|
|
role_id= fields.Many2one(comodel_name='property.role', string='Roles',)
|
|
property_id = fields.Many2one('internal.property', string='Property', required=True, ondelete='cascade')
|
|
no_of_house = fields.Integer(string='No of Housees')
|
|
no_of_shop = fields.Integer(string='No of Shops')
|
|
no_of_other_unit = fields.Integer(string='No of Other Units')
|
|
description = fields.Char(string='Description')
|
|
total = fields.Integer(string='Total', compute='_compute_total', store=True, readonly=True)
|
|
|
|
@api.depends('no_of_house', 'no_of_shop', 'no_of_other_unit')
|
|
def _compute_total(self):
|
|
for record in self:
|
|
record.total = record.no_of_house + record.no_of_shop + record.no_of_other_unit
|
|
|