- hello world
+
+
Viraj Warhade
+
VIWAR-ODOO
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/awesome_owl/views/counter_template.xml b/awesome_owl/views/counter_template.xml
new file mode 100644
index 00000000000..43fd45bf7e9
--- /dev/null
+++ b/awesome_owl/views/counter_template.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/views/templates.xml b/awesome_owl/views/templates.xml
index aa54c1a7241..3557a50dab2 100644
--- a/awesome_owl/views/templates.xml
+++ b/awesome_owl/views/templates.xml
@@ -1,15 +1,32 @@
-
+
+
-
-
+
diff --git a/estate/__init__.py b/estate/__init__.py
new file mode 100644
index 00000000000..0650744f6bc
--- /dev/null
+++ b/estate/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/estate/__manifest__.py b/estate/__manifest__.py
new file mode 100644
index 00000000000..3e64de231b7
--- /dev/null
+++ b/estate/__manifest__.py
@@ -0,0 +1,28 @@
+{
+ 'name': 'Real Estate',
+ 'version': '1.0',
+ 'depends': ['base','mail'],
+ 'author': 'viwar-odoo',
+ 'category': 'real estate',
+ 'description': "real estate App.",
+ "data": [
+ "security/security_groups.xml",
+ "security/ir.model.access.csv",
+ "views/estate_maintainance_form.xml",
+ "views/estate_property_inherited.xml",
+ "views/estate_property_visit_calender.xml",
+ "views/estate_propert_offer_wizard.xml",
+ "views/estate_property_visit_kanban_view.xml",
+ "views/estate_property_offer_view.xml",
+ "views/estate_property_tag_view.xml",
+ "views/estate_property_type_view.xml",
+ "views/estate_property_visit_action.xml",
+ "views/estate_property_view.xml",
+ "views/estate_menus.xml",
+ # "data/estate_property_demo.xml"
+ ],
+ 'application': True,
+ 'installable': True,
+ 'license': 'LGPL-3',
+ 'website': 'https://odoo.com',
+}
diff --git a/estate/data/estate_property_demo.xml b/estate/data/estate_property_demo.xml
new file mode 100644
index 00000000000..423b7e57e6a
--- /dev/null
+++ b/estate/data/estate_property_demo.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ 1
+ tag1
+
+
+
+ 1
+ type1
+
+
+
+ 1
+ property
+
+ 1
+
+
+
+ 1
+ 100000
+ 1
+
+
+
+
+
+
+
+ Marc Demo
+
+ YourCompany
+ 3575 Buena Vista Avenue
+ Eugene
+
+ 97401
+
+ Europe/Brussels
+ mark.brown23@example.com
+ (441)-695-2334
+
+
+
+
+
diff --git a/estate/models/__init__.py b/estate/models/__init__.py
new file mode 100644
index 00000000000..1326ed3caea
--- /dev/null
+++ b/estate/models/__init__.py
@@ -0,0 +1,8 @@
+from . import estate_maintainance_request
+from . import estate_property
+from . import estate_property_offer
+from . import estate_property_offer_wizard
+from . import estate_property_tag
+from . import estate_property_type
+from . import estate_property_visit
+from . import inherited
\ No newline at end of file
diff --git a/estate/models/estate_maintainance_request.py b/estate/models/estate_maintainance_request.py
new file mode 100644
index 00000000000..4b8f11ec6b2
--- /dev/null
+++ b/estate/models/estate_maintainance_request.py
@@ -0,0 +1,37 @@
+from odoo import api, models, fields
+
+
+class EstateMaintainanceRequest(models.Model):
+ _name = 'estate.maintainance.request'
+ _description = 'table for the technician maintainance request'
+
+ property_id = fields.Many2one('estate.property', required=True, readonly=True)
+ buyer_id = fields.Many2one('res.users', required=True, default='self.env.uid')
+ technician_id = fields.Many2one('res.partner', required=False)
+ state = fields.Selection(
+ string='state',
+ default='new',
+ selection=[
+ ('new', "New"),
+ ('assigned', "Assigned"),
+ ('inprogress', "Inprogress"),
+ ('done', "Done"),
+ ('cancelled', "Cancelled"),
+ ],
+ )
+ estimate_cost = fields.Float(required=False)
+ actual_cost = fields.Float(compute='_compute_actual_cost', store=True)
+ current_stage = fields.Char(compute='_compute_state_after_assigned', store=True)
+
+ @api.depends('state', 'estimate_cost')
+ def _compute_actual_cost(self):
+ if self.state == 'done':
+ self.actual_cost = self.estimate_cost * 1.18
+
+ @api.depends('technician_id', 'state')
+ def _compute_state_after_assigned(self):
+ if self.state == 'new' and self.technician_id:
+ self.state = 'assigned'
+ self.current_stage = 'assigned'
+ else:
+ self.current_stage = self.state
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
new file mode 100644
index 00000000000..be99fe42ba3
--- /dev/null
+++ b/estate/models/estate_property.py
@@ -0,0 +1,212 @@
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError, ValidationError
+from odoo.tools.float_utils import float_compare
+
+
+class EstateProperty(models.Model):
+ _name = 'estate.property'
+ _description = 'Test Model for real estate'
+ _check_positive_expected_price = models.Constraint(
+ 'CHECK (expected_price >= 0)', 'expected_price should be positive'
+ )
+ _check_positive_selling_price = models.Constraint(
+ 'CHECK (selling_price >= 0)', 'selling_price should be positive'
+ )
+ _order = 'id desc'
+ _inherit = ['mail.thread']
+
+ name = fields.Char(default='Unknown', tracking=True)
+ last_seen = fields.Datetime('Last Seen', default=fields.Datetime.now)
+ description = fields.Text()
+ postcode = fields.Char()
+ date_availability = fields.Date(
+ copy=False, default=fields.Date.add(fields.Date.today(), months=3)
+ )
+ expected_price = fields.Float()
+ selling_price = fields.Float(copy=False, readonly=True, default=0)
+ bedrooms = fields.Integer(default=2)
+ living_area = fields.Integer()
+ facades = fields.Integer()
+ garage = fields.Boolean()
+ garden = fields.Boolean()
+ state = fields.Selection(
+ string='status',
+ selection=[
+ ('new', "New"),
+ ('offer received', "Offer Received"),
+ ('offer accepted', "Offer Accepted"),
+ ('sold', "Sold"),
+ ('cancelled', "Cancelled"),
+ ],
+ default='new',
+ compute='_compute_statusbar',
+ store=True,
+ tracking=True,
+ )
+ active = fields.Boolean(default=True)
+ garden_area = fields.Integer()
+ garden_orientation = fields.Selection(
+ string='garden orientation direction',
+ selection=[
+ ('north', "North"),
+ ('south', "South"),
+ ('east', "East"),
+ ('west', "West"),
+ ],
+ )
+ property_type_id = fields.Many2one('estate.property.type')
+ salesperson_id = fields.Many2one(
+ 'res.users',
+ string='Salesperson',
+ index=True,
+ default=lambda self: self.env.user,
+ )
+ buyer_id = fields.Many2one('res.partner', default='None', copy=False)
+ tag_ids = fields.Many2many('estate.property.tag')
+ offer_ids = fields.One2many('estate.property.offer', 'property_id', string='offers')
+ total_area = fields.Float(compute='_compute_total_area')
+ max_offer_price = fields.Float(
+ default=None, compute='_compute_max_offer_price', store=True
+ )
+ estate_maintainance_id = fields.One2many(
+ 'estate.maintainance.request', 'property_id'
+ )
+ visit_ids = fields.One2many('estate.property.visit', 'property_id', string='visits')
+
+ @api.depends('living_area', 'garden_area')
+ def _compute_total_area(self):
+ for record in self:
+ record.total_area = record.living_area + record.garden_area
+
+ @api.onchange('garden')
+ def _onchange_garden(self):
+ if self.garden:
+ self.garden_area = 10
+ self.garden_orientation = 'north'
+ else:
+ self.garden_area = None
+ self.garden_orientation = None
+
+ def button_cancel(self):
+ if self.state == 'cancelled':
+ raise UserError('The property is already cancelled')
+ elif self.state == 'sold':
+ raise UserError('The property is already sold, you cannot cancel it')
+ else:
+ self.state = 'cancelled'
+
+ def button_sold(self):
+ if self.state == 'sold':
+ raise UserError('The property is already sold')
+ elif self.state == 'cancelled':
+ raise UserError('The property is already cancelled, and cannot be sold')
+ else:
+ self.state = 'sold'
+
+ subject = _('Property Sold: %s') % self.name
+ body = _('''
+
Hello,
+
The property %s has been successfully sold for %s.
+
+
+
+ | Role |
+ Name |
+
+
+ | Seller |
+ %s |
+
+
+ | Buyer |
+ %s |
+
+
+
+
This is an automated message.
+ ''') % (
+ self.name,
+ self.selling_price,
+ self.salesperson_id.name,
+ self.buyer_id.name or 'N/A',
+ )
+ mail_adrs = []
+ if self.salesperson_id:
+ mail_adrs.append(self.salesperson_id.partner_id.id)
+ if self.buyer_id:
+ mail_adrs.append(self.buyer_id.id)
+
+ ctx = {
+ 'default_model': 'estate.property',
+ 'default_res_ids': self.ids,
+ 'default_composition_mode': 'comment',
+ 'default_email_layout_xmlid': 'mail.mail_notification_layout_with_responsible_signature',
+ 'default_subject': subject,
+ 'default_body': body,
+ 'default_partner_ids': mail_adrs,
+ 'email_notification_allow_footer': True,
+ 'hide_mail_template_management_options': False,
+ }
+
+ return {
+ 'name': _('Send Confirmation Email'),
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'mail.compose.message',
+ 'view_mode': 'form',
+ 'views': [(False, 'form')],
+ 'view_id': False,
+ 'target': 'new',
+ 'context': ctx,
+ }
+
+ @api.depends('offer_ids.price')
+ def _compute_max_offer_price(self):
+ for rec in self:
+ if rec.offer_ids:
+ new_max_offer_price = max(rec.offer_ids.mapped('price'))
+ if rec.max_offer_price != new_max_offer_price:
+ rec.max_offer_price = new_max_offer_price
+
+ def accept_best_offer(self):
+ for offers in self.offer_ids:
+ if self.max_offer_price == offers.price:
+ offers.action_accept_offer()
+ ##################################
+ # old code
+ # offers.status = 'accepted'
+ # self.state = 'offer accepted'
+ # self.buyer_id = offers.partner_id
+ # self.selling_price = offers.price
+ ##################################
+ else:
+ offers.status = 'refused'
+
+ @api.depends('offer_ids')
+ def _compute_statusbar(self):
+ for record in self:
+ if record.offer_ids and record.state == 'new':
+ record.state = 'offer received'
+
+ @api.constrains('selling_price', 'expected_price')
+ def check_percentage(self):
+ for record in self:
+ if record.selling_price and record.expected_price:
+ if (
+ float_compare(
+ record.selling_price,
+ record.expected_price * 0.9,
+ precision_digits=1,
+ )
+ < 0
+ ):
+ raise ValidationError(
+ 'the selling perice cant be less than 90% of the expected price'
+ )
+
+ @api.ondelete(at_uninstall=False)
+ def _unlink_if_user_inactive(self):
+ for record in self:
+ if record.state not in ['cancelled', 'new']:
+ raise UserError(
+ 'cannot delete - only delete from the state `new` and `cancelled`'
+ )
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
new file mode 100644
index 00000000000..746b6782286
--- /dev/null
+++ b/estate/models/estate_property_offer.py
@@ -0,0 +1,158 @@
+from odoo import fields, models, api
+from odoo.exceptions import UserError
+
+
+class EstatePropertyOffer(models.Model):
+ _name = 'estate.property.offer'
+ _description = 'estate property offer'
+ _check_positive_offer_price = models.Constraint(
+ 'CHECK (price >= 0)', 'price should be positive'
+ )
+ _order = 'price desc'
+
+ price = fields.Float(copy=False)
+ status = fields.Selection(
+ copy=False,
+ string='status',
+ selection=[('accepted', "Accepted"), ('refused', "Refused")],
+ )
+ partner_id = fields.Many2one(
+ 'res.partner', required=True, default=lambda self: self.env.user.partner_id.id
+ )
+ property_id = fields.Many2one('estate.property', required=True)
+ validity = fields.Integer(default=7, copy=False)
+ deadline = fields.Date(
+ compute='_compute_deadline', store=True, inverse='_inverse_deadline'
+ )
+
+ property_type_id = fields.Many2one(
+ 'estate.property.type', related='property_id.property_type_id', store=True
+ )
+
+ @api.depends('validity', 'deadline')
+ def _compute_deadline(self):
+ for record in self:
+ record.deadline = fields.Date.add(fields.Date.today(), days=record.validity)
+
+ def _inverse_deadline(self):
+ for record in self:
+ today_date = fields.Date.today()
+ record.validity = (record.deadline - today_date).days
+
+ def action_accept_offer(self):
+ for offer in self:
+ for offer.property_id in offer.property_id:
+ if (
+ offer.property_id.state == 'offer accepted'
+ or offer.property_id.state == 'sold'
+ ):
+ raise UserError(
+ 'An offer has already been accepted for this property.'
+ )
+ else:
+ offer.status = 'accepted'
+ offer.property_id.state = 'offer accepted'
+ offer.property_id.buyer_id = offer.partner_id
+ offer.property_id.selling_price = offer.price
+
+ won_stage_id = self.env['crm.stage'].search(
+ [('name', 'in', ['Won'])]
+ )
+ self.env['crm.lead'].create(
+ {
+ 'create_date': fields.Date,
+ 'display_name': 'test',
+ 'expected_revenue': self.price,
+ 'name': 'test',
+ 'type': 'opportunity',
+ 'won_status': 'won',
+ 'probability': 100,
+ 'stage_id': won_stage_id.id,
+ }
+ )
+ self.env.cr.commit()
+
+ for offers in self.property_id.offer_ids:
+ if offers != self:
+ offers.status = 'refused'
+
+ lead = self.env['crm.lead'].create(
+ {
+ 'name': 'test123',
+ 'expected_revenue': self.price,
+ 'won_status': 'lost',
+ 'create_date': fields.Date,
+ 'display_name': 'test',
+ 'name': 'test',
+ 'type': 'opportunity',
+ 'won_status': 'lost',
+ 'probability': 0,
+ }
+ )
+ lead.action_set_lost()
+ self.env.cr.commit()
+
+ def action_reject_offer(self):
+ for offer in self:
+ if offer.status == 'accepted':
+ if offer.property_id.state == 'sold':
+ raise UserError(
+ 'the property is sold. CANT REJECT THE OFFER - THANK YOU'
+ )
+ else:
+ offer.status = 'refused'
+ offer.property_id.state = 'offer received'
+ offer.property_id.buyer_id = False
+ offer.property_id.selling_price = 0
+
+ elif offer.status == 'refused':
+ raise UserError('This offer has already been refused.')
+ else:
+ offer.status = 'refused'
+
+ lead = self.env['crm.lead'].create(
+ {
+ 'name': 'test123',
+ 'won_status': 'lost',
+ 'expected_revenue': self.price,
+ 'create_date': fields.Date,
+ 'display_name': 'test',
+ 'name': 'test',
+ 'type': 'opportunity',
+ 'won_status': 'won',
+ 'probability': 0,
+ }
+ )
+ lead.action_set_lost()
+ self.env.cr.commit()
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ property_id = self.env['estate.property'].browse(vals.get('property_id'))
+ if property_id.offer_ids:
+ max_offer_price = max(property_id.offer_ids.mapped('price'))
+ if vals.get('price', 0) <= max_offer_price:
+ raise UserError(
+ 'The offer price must be higher than the current highest offer (%s).'
+ % max_offer_price
+ )
+ property_id.state = 'offer received'
+ return super().create(vals_list)
+
+ @api.model
+ def _cron_expired_offer(self):
+ '''
+ Cron job to set expired property offers to 'refused' status.
+ '''
+ print('cronran')
+ today = fields.Date.today()
+ offers_expired = self.search(
+ [('deadline', '<', today), ('status', '!=', 'refused')]
+ )
+ if offers_expired:
+ offers_expired.write({'status': 'refused'})
+ self.env.cr.commit()
+ print('offers set to refused by cron job')
+ else:
+ print('cron job ran : no offers exceeding the deadline')
diff --git a/estate/models/estate_property_offer_wizard.py b/estate/models/estate_property_offer_wizard.py
new file mode 100644
index 00000000000..e9fbb5297d3
--- /dev/null
+++ b/estate/models/estate_property_offer_wizard.py
@@ -0,0 +1,38 @@
+from odoo import fields, models, api
+
+
+class EstatePropertyOfferWizard(models.TransientModel):
+ _name = 'estate.property.offer.wizard'
+ _description = 'estate.property.offer.wizard model for the wizard'
+
+ price = fields.Float(string='Offer Price', required=True)
+
+ partner_id = fields.Many2one('res.partner', string='Buyer', required=True)
+
+ def action_create_offers(self):
+ """
+
+ properties that are not Sold or Cancelled thn also creates an offer for each
+
+ """
+
+ self.ensure_one()
+
+ new_offer = []
+ eligible_property = self.env['estate.property'].search(
+ [('state', 'not in', ['sold', 'cancelled', 'offer accepted'])]
+ )
+
+ for prop in eligible_property:
+ new_offer.append(
+ {
+ 'price': self.price,
+ 'partner_id': self.partner_id.id,
+ 'property_id': prop.id,
+ }
+ )
+
+ if new_offer:
+ self.env['estate.property.offer'].create(new_offer)
+
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
new file mode 100644
index 00000000000..3c9d5fe31cb
--- /dev/null
+++ b/estate/models/estate_property_tag.py
@@ -0,0 +1,11 @@
+from odoo import fields, models
+
+
+class EstatePropertytTag(models.Model):
+ _name = 'estate.property.tag'
+ _description = 'Estate_property_tag'
+ _unique_tag = models.UniqueIndex('(name)', 'The name of the tag must be unique')
+ _order = 'name asc'
+
+ name = fields.Char(required=True)
+ color = fields.Integer()
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
new file mode 100644
index 00000000000..faa784b7ed1
--- /dev/null
+++ b/estate/models/estate_property_type.py
@@ -0,0 +1,21 @@
+from odoo import fields, models, api
+
+
+class EstatePropertyType(models.Model):
+ _name = 'estate.property.type'
+ _description = 'Estate_property_type'
+ _unique_type = models.UniqueIndex('(name)', 'property type should be unique')
+ _order = 'sequence'
+
+ name = fields.Char(required=True)
+ sequence = fields.Integer(
+ default=1, help='Used to order the property types. Lower is better.'
+ )
+ offer_ids = fields.One2many('estate.property.offer', 'property_type_id')
+ offer_count = fields.Integer(default=0, compute='_compute_total_count', store=True)
+ property_ids = fields.One2many('estate.property', 'property_type_id')
+
+ @api.depends('offer_ids')
+ def _compute_total_count(self):
+ for rec in self:
+ rec.offer_count = len(rec.offer_ids.mapped('id'))
diff --git a/estate/models/estate_property_visit.py b/estate/models/estate_property_visit.py
new file mode 100644
index 00000000000..07ac4046892
--- /dev/null
+++ b/estate/models/estate_property_visit.py
@@ -0,0 +1,35 @@
+from odoo import api, fields, models
+from odoo.exceptions import ValidationError
+
+
+class EstatePropertyVisit(models.Model):
+ _name = 'estate.property.visit'
+ _description = 'estate property visit'
+ _order = 'date desc'
+ _sql_const = models.UniqueIndex(
+ '(date, property_id)',
+ 'This property is already booked for a visit on this date!',
+ )
+
+ property_id = fields.Many2one('estate.property', required=True)
+ visitor_id = fields.Many2one('res.partner', required=True)
+ date = fields.Date(required=True)
+ comment = fields.Text()
+ state = fields.Selection(
+ string='status',
+ selection=[
+ ('new', "New"),
+ ('scheduled', "Scheduled"),
+ ('done', "Done"),
+ ('cancelled', "Cancelled"),
+ ],
+ default='new',
+ )
+
+ @api.constrains('date')
+ def _compute_date_clash(self):
+ for rec in self:
+ if rec.date < fields.Date.today():
+ raise ValidationError('The visit date cannot be in the past.')
+ if rec.date:
+ rec.state = 'scheduled'
diff --git a/estate/models/inherited.py b/estate/models/inherited.py
new file mode 100644
index 00000000000..1d11733292b
--- /dev/null
+++ b/estate/models/inherited.py
@@ -0,0 +1,7 @@
+from odoo import fields, models
+
+
+class Inherited(models.Model):
+ _inherit = 'res.users'
+
+ property_ids = fields.One2many('estate.property', 'salesperson_id')
diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv
new file mode 100644
index 00000000000..2f3aa025e20
--- /dev/null
+++ b/estate/security/ir.model.access.csv
@@ -0,0 +1,26 @@
+id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
+access_estate_property_admin,estate_property_admin,model_estate_property,estate.property_admin,1,1,1,1
+access_estate_maintainance_request,access_estate_maintainance_request,model_estate_maintainance_request,estate.property_admin,1,1,1,1
+access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,estate.property_admin,1,1,1,1
+access_estate_property_visit,access_estate_property_visit,model_estate_property_visit,estate.property_admin,1,1,1,1
+access_estate_property_type,access_estate_property_type,model_estate_property_type,estate.property_admin,1,1,1,1
+access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,estate.property_admin,1,1,1,1
+access_estate_property_agent,estate_property_agent,model_estate_property,estate.property_agent,1,1,1,0
+access_estate_property_offer_agent,estate_property_offer_agent,model_estate_property_offer,estate.property_agent,1,1,1,0
+access_estate_maintainance_request_agent,access_estate_maintainance_request_agent,model_estate_maintainance_request,estate.property_agent,1,0,0,0
+access_estate_property_visit_agent,access_estate_property_visit_agent,model_estate_property_visit,estate.property_agent,1,0,0,0
+access_estate_property_type_agent,access_estate_property_type_agent,model_estate_property_type,estate.property_agent,1,1,1,0
+access_estate_property_tag_agent,access_estate_property_tag_agent,model_estate_property_tag,estate.property_agent,1,1,1,0
+access_estate_property_manager,estate_property_manager,model_estate_property,estate.property_manager,1,1,1,1
+access_estate_property_offer_manager,estate_property_offer_manager,model_estate_property_offer,estate.property_manager,1,1,1,1
+access_estate_maintainance_request_manager,access_estate_maintainance_request_manager,model_estate_maintainance_request,estate.property_manager,1,0,0,0
+access_estate_property_visit_manager,access_estate_property_visit_manager,model_estate_property_visit,estate.property_manager,1,1,1,1
+access_estate_property_type_manager,access_estate_property_type_manager,model_estate_property_type,estate.property_manager,1,1,1,1
+access_estate_property_tag_manager,access_estate_property_tag_manager,model_estate_property_tag,estate.property_manager,1,1,1,1
+access_estate_property_maintainance_staff,estate_property_maintainance_staff,model_estate_property,estate.property_maintainance_staff,1,1,0,0
+access_estate_property_offer_maintainance_staff,estate_property_offer_maintainance_staff,model_estate_property_offer,estate.property_maintainance_staff,1,0,0,0
+access_estate_maintainance_request_maintainance_staff,access_estate_maintainance_request_maintainance_staff,model_estate_maintainance_request,estate.property_maintainance_staff,1,1,1,1
+access_estate_property_visit_maintainance_staff,access_estate_property_visit_maintainance_staff,model_estate_property_visit,estate.property_maintainance_staff,1,0,0,0
+access_estate_property_type_maintainance_staff,access_estate_property_type_maintainance_staff,model_estate_property_type,estate.property_maintainance_staff,1,0,0,0
+access_estate_property_tag_maintainance_staff,access_estate_property_tag_maintainance_staff,model_estate_property_tag,estate.property_maintainance_staff,1,0,0,0
+access_estate_property_offer_wizard_all,access_estate_property_offer_wizard_all,model_estate_property_offer_wizard,base.group_user,1,1,1,1
\ No newline at end of file
diff --git a/estate/security/security_groups.xml b/estate/security/security_groups.xml
new file mode 100644
index 00000000000..3484a3e8a8b
--- /dev/null
+++ b/estate/security/security_groups.xml
@@ -0,0 +1,27 @@
+
+
+
+ Real Estate
+
+
+ Admin
+
+
+
+
+
+ Agent
+
+
+
+
+ Manager
+
+
+
+
+ Maintainance Staff
+
+
+
+
diff --git a/estate/static/icon/icon.jpeg b/estate/static/icon/icon.jpeg
new file mode 100644
index 00000000000..bad92496397
Binary files /dev/null and b/estate/static/icon/icon.jpeg differ
diff --git a/estate/views/estate_maintainance_form.xml b/estate/views/estate_maintainance_form.xml
new file mode 100644
index 00000000000..6ffe1a5b44d
--- /dev/null
+++ b/estate/views/estate_maintainance_form.xml
@@ -0,0 +1,42 @@
+
+
+
+ estate_maintainance_action
+ estate.maintainance.request
+ list,form
+
+
+ estate.maintainance.type.list.view
+ estate.maintainance.request
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate.maintainance.type.form.view
+ estate.maintainance.request
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml
new file mode 100644
index 00000000000..457f2173fbd
--- /dev/null
+++ b/estate/views/estate_menus.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_propert_offer_wizard.xml b/estate/views/estate_propert_offer_wizard.xml
new file mode 100644
index 00000000000..b1c3e9b4f05
--- /dev/null
+++ b/estate/views/estate_propert_offer_wizard.xml
@@ -0,0 +1,26 @@
+
+
+
+ Launch the Wizard
+ estate.property.offer.wizard
+ form
+ new
+
+
+ estate_property_offer_form_wizard
+ estate.property.offer.wizard
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_inherited.xml b/estate/views/estate_property_inherited.xml
new file mode 100644
index 00000000000..3b1f8af125d
--- /dev/null
+++ b/estate/views/estate_property_inherited.xml
@@ -0,0 +1,13 @@
+
+
+
+ inherited.model.form.inherit
+ res.users
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml
new file mode 100644
index 00000000000..ad2bf4a95a5
--- /dev/null
+++ b/estate/views/estate_property_offer_view.xml
@@ -0,0 +1,54 @@
+
+
+
+ estate_property_offer
+ estate.property.offer
+ list,form
+
+
+ offers
+ estate.property.offer
+ list,form
+ [('property_type_id', '=', active_id)]
+
+
+ estate.property.offer.list.view
+ estate.property.offer
+
+
+
+
+
+
+
+
+
+
+ estate.property.offer.form.view
+ estate.property.offer
+
+
+
+
+
+ expire_offer
+
+ code
+ model._cron_expired_offer()
+
+ 1
+ minutes
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_tag_view.xml b/estate/views/estate_property_tag_view.xml
new file mode 100644
index 00000000000..1fa40197226
--- /dev/null
+++ b/estate/views/estate_property_tag_view.xml
@@ -0,0 +1,30 @@
+
+
+
+ estate_property_tag
+ estate.property.tag
+ list,form
+
+
+ estate.property.tag.list.view
+ estate.property.tag
+
+
+
+
+
+
+
+ estate.property.tag.form.view
+ estate.property.tag
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_type_view.xml b/estate/views/estate_property_type_view.xml
new file mode 100644
index 00000000000..420c9f7b4d6
--- /dev/null
+++ b/estate/views/estate_property_type_view.xml
@@ -0,0 +1,51 @@
+
+
+
+ estate_property_type_action
+ estate.property.type
+ list,form
+
+
+ estate.property.type.tree.view
+ estate.property.type
+
+
+
+
+
+
+
+
+
+ estate.property.type.form.view
+ estate.property.type
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_view.xml b/estate/views/estate_property_view.xml
new file mode 100644
index 00000000000..eab1bc60dff
--- /dev/null
+++ b/estate/views/estate_property_view.xml
@@ -0,0 +1,177 @@
+
+
+
+ Real-estate Property"s
+ estate.property
+ {'search_default_available': 1}
+ kanban,list,form
+
+
+ estate.property.list.view
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate.property.form.view
+ estate.property
+
+
+
+
+
+ estate.property.search.view
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/estate/views/estate_property_visit_action.xml b/estate/views/estate_property_visit_action.xml
new file mode 100644
index 00000000000..aa9cad9c57f
--- /dev/null
+++ b/estate/views/estate_property_visit_action.xml
@@ -0,0 +1,39 @@
+
+
+
+ estate_property_visit
+ estate.property.visit
+ list,form
+
+
+ estate.property.visit.list.view
+ estate.property.visit
+
+
+
+
+
+
+
+
+
+
+
+ estate.property.visit.form.view
+ estate.property.visit
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_visit_calender.xml b/estate/views/estate_property_visit_calender.xml
new file mode 100644
index 00000000000..a05fd994c50
--- /dev/null
+++ b/estate/views/estate_property_visit_calender.xml
@@ -0,0 +1,16 @@
+
+
+
+ Real-estate Property"s
+ estate.property.visit
+ calendar,form,list
+
+
+ property
+ estate.property.visit
+
+
+
+
+
+
diff --git a/estate/views/estate_property_visit_kanban_view.xml b/estate/views/estate_property_visit_kanban_view.xml
new file mode 100644
index 00000000000..13ac8a59f56
--- /dev/null
+++ b/estate/views/estate_property_visit_kanban_view.xml
@@ -0,0 +1,62 @@
+
+
+
+ Real-estate Property"s
+ estate.property
+ kanban,form,list
+
+
+ estate.property.view.kanban
+ estate.property
+
+
+
+
+
+
+
+
+
+
+ selling price:
+
+
+
+
+
+ expected:
+
+
+
+
+ max offer:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate_account/__init__.py b/estate_account/__init__.py
new file mode 100644
index 00000000000..9a7e03eded3
--- /dev/null
+++ b/estate_account/__init__.py
@@ -0,0 +1 @@
+from . import models
\ No newline at end of file
diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py
new file mode 100644
index 00000000000..473225cc196
--- /dev/null
+++ b/estate_account/__manifest__.py
@@ -0,0 +1,16 @@
+{
+ 'name': 'Estate Accounts',
+ 'version': '1.0',
+ 'depends': ['estate','account'],
+ 'author': 'viwar-odoo',
+ 'category': 'Accounts',
+ 'description': "estate_account",
+ "data": [
+ "views/menu.xml",
+ ],
+ 'application': True,
+ 'installable': True,
+ 'auto_install': True,
+ 'license': 'LGPL-3',
+ 'website': 'https://odoo.com',
+}
diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py
new file mode 100644
index 00000000000..5e1963c9d2f
--- /dev/null
+++ b/estate_account/models/__init__.py
@@ -0,0 +1 @@
+from . import estate_property
diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py
new file mode 100644
index 00000000000..eb12d37f3eb
--- /dev/null
+++ b/estate_account/models/estate_property.py
@@ -0,0 +1,34 @@
+from odoo import fields, models, Command
+
+
+class EstateProperty(models.Model):
+ _name = 'estate.property'
+ _description = 'EstateProperty'
+ _inherit = 'estate.property'
+
+ def button_sold(self):
+ selling_price = self.selling_price * 0.06
+ self.env['account.move'].create(
+ {
+ 'partner_id': self.buyer_id.id,
+ 'move_type': 'out_invoice',
+ 'line_ids': [
+ Command.create(
+ {
+ 'name': '6% of selling price',
+ 'quantity': 1,
+ 'price_unit': selling_price,
+ }
+ ),
+ Command.create(
+ {
+ 'name': 'Administrative Fees',
+ 'quantity': 1,
+ 'price_unit': 100.00,
+ }
+ ),
+ ],
+ }
+ )
+ print('WORKS!!')
+ return super().button_sold()
diff --git a/estate_account/security/ir.model.access.csv b/estate_account/security/ir.model.access.csv
new file mode 100644
index 00000000000..7f2dab069ce
--- /dev/null
+++ b/estate_account/security/ir.model.access.csv
@@ -0,0 +1 @@
+access_test_model_manager,test_model_manager,model_test_model,,1,1,1,1
diff --git a/estate_account/views/menu.xml b/estate_account/views/menu.xml
new file mode 100644
index 00000000000..018cccd78fe
--- /dev/null
+++ b/estate_account/views/menu.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/myportal/__init__.py b/myportal/__init__.py
new file mode 100644
index 00000000000..91c5580fed3
--- /dev/null
+++ b/myportal/__init__.py
@@ -0,0 +1,2 @@
+from . import controllers
+from . import models
diff --git a/myportal/__manifest__.py b/myportal/__manifest__.py
new file mode 100644
index 00000000000..7b105e79d53
--- /dev/null
+++ b/myportal/__manifest__.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "supplier_portal",
+
+ 'summary': """
+ supplier portal TASK - demo task"
+ """,
+
+ 'description': """
+ supplier portal TASK - demo task"
+ """,
+
+ 'author': "Odoo",
+ 'website': "https://www.odoo.com",
+
+ # Categories can be used to filter modules in modules listing
+ # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
+ # for the full list
+ 'category': 'supplier_portal',
+ 'version': '0.1',
+
+ 'depends': ['base', 'web'],
+ 'application': True,
+ 'installable': True,
+ 'data': [
+ 'views/templates.xml',
+
+ ],
+ 'assets': {
+ 'myportal.assets_playground': [
+ ('include', 'web._assets_helpers'),
+ ('include', 'web._assets_backend_helpers'),
+ 'web/static/src/scss/pre_variables.scss',
+ 'web/static/lib/bootstrap/scss/_variables.scss',
+ 'web/static/lib/bootstrap/scss/_maps.scss',
+ ('include', 'web._assets_bootstrap'),
+ ('include', 'web._assets_core'),
+ 'web/static/src/libs/fontawesome/css/font-awesome.css',
+ 'myportal/static/src/**/*',
+ ],
+ },
+ 'license': 'AGPL-3'
+}
diff --git a/myportal/controllers/__init__.py b/myportal/controllers/__init__.py
new file mode 100644
index 00000000000..457bae27e11
--- /dev/null
+++ b/myportal/controllers/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import controllers
\ No newline at end of file
diff --git a/myportal/controllers/controllers.py b/myportal/controllers/controllers.py
new file mode 100644
index 00000000000..e957a1f839b
--- /dev/null
+++ b/myportal/controllers/controllers.py
@@ -0,0 +1,10 @@
+from odoo import http
+from odoo.http import request, route
+
+class OwlPlayground(http.Controller):
+ @http.route(['/portal'], type='http', auth='public')
+ def show_playground(self):
+ """
+ Renders the portal page
+ """
+ return request.render('myportal.playground',{})
\ No newline at end of file
diff --git a/myportal/models/__init__.py b/myportal/models/__init__.py
new file mode 100644
index 00000000000..e3e219b1a71
--- /dev/null
+++ b/myportal/models/__init__.py
@@ -0,0 +1 @@
+from . import portal
\ No newline at end of file
diff --git a/myportal/models/portal.py b/myportal/models/portal.py
new file mode 100644
index 00000000000..3d2cd8419b0
--- /dev/null
+++ b/myportal/models/portal.py
@@ -0,0 +1,9 @@
+from odoo import api, models, fields
+
+
+class Portal(models.Model):
+ _name = "portal"
+ _description = "portal model"
+ #_inherit = "res.partner"
+
+ name = fields.Char(default="test name",required=True)
\ No newline at end of file
diff --git a/myportal/security/ir.model.access.csv b/myportal/security/ir.model.access.csv
new file mode 100644
index 00000000000..5aaaffcc991
--- /dev/null
+++ b/myportal/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
+portal_id,portal_name,model_portal,base.group_user,1,1,1,1
\ No newline at end of file
diff --git a/myportal/static/src/main.js b/myportal/static/src/main.js
new file mode 100644
index 00000000000..9ce84e6f609
--- /dev/null
+++ b/myportal/static/src/main.js
@@ -0,0 +1,10 @@
+import { whenReady } from "@odoo/owl";
+import { mountComponent } from "@web/env";
+import {Playground} from "./playground"
+
+const config = {
+ dev: true,
+ name: "Supplier Portal",
+};
+
+whenReady(() => mountComponent(Playground, document.body, config));
diff --git a/myportal/static/src/playground.js b/myportal/static/src/playground.js
new file mode 100644
index 00000000000..163a2dde2c2
--- /dev/null
+++ b/myportal/static/src/playground.js
@@ -0,0 +1,24 @@
+import { Component, useRef, useState, onWillStart, useEffect, onWillUnmount } from "@odoo/owl";
+import { ControlPanel } from "@web/search/control_panel/control_panel"
+import { Layout } from "@web/search/layout";
+import { registry } from "@web/core/registry";
+import { useService } from "@web/core/utils/hooks";
+import { _t } from "@web/core/l10n/translation";
+
+
+export class Playground extends Component {
+ static template = "myportal.playground";
+
+ //static components = {
+ // Layout,
+ //};
+
+ static props = {
+ };
+
+
+ setup() {
+
+ }
+
+}
\ No newline at end of file
diff --git a/myportal/static/src/playground.xml b/myportal/static/src/playground.xml
new file mode 100644
index 00000000000..529a7c8c57f
--- /dev/null
+++ b/myportal/static/src/playground.xml
@@ -0,0 +1,6 @@
+
+
+
+ Hello
+
+
diff --git a/myportal/views/menu.xml b/myportal/views/menu.xml
new file mode 100644
index 00000000000..6b8e67534f6
--- /dev/null
+++ b/myportal/views/menu.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myportal/views/portal_view.xml b/myportal/views/portal_view.xml
new file mode 100644
index 00000000000..7b0fa68a6ec
--- /dev/null
+++ b/myportal/views/portal_view.xml
@@ -0,0 +1,51 @@
+
+
+
+ portal
+ portal
+ kanban,list,form
+
+
+ portal.list.view
+ portal
+
+
+
+
+
+
+
+
+
+ portal.form.view
+ portal
+
+
+
+
+
+
diff --git a/myportal/views/templates.xml b/myportal/views/templates.xml
new file mode 100644
index 00000000000..6118efe1af6
--- /dev/null
+++ b/myportal/views/templates.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+