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..a2b50536420 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,24 @@ +{ + 'name': 'RealEstate', + 'version': '1.0', + 'category': 'Real Estate/Brokerage', + 'summary': 'A module to manage real estate advertisements and property offers', + 'description': """A simple module to manage real estate ads.List your properties, track details like bedrooms and garden,let buyers make offers, and accept or reject them.""", + 'author': 'Pranjali Sangavekar(prsan)', + 'license': 'LGPL-3', + 'depends': ['base', 'mail'], + 'data': [ + 'security/security.xml', + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_property_offers_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tags_views.xml', + 'views/estate_res_users_view.xml', + 'views/estate_menus.xml', + ], + 'demo': [ + 'demo/estate_property_data.xml', + ], + 'application': True, +} diff --git a/estate/demo/estate_property_data.xml b/estate/demo/estate_property_data.xml new file mode 100644 index 00000000000..4861185e147 --- /dev/null +++ b/estate/demo/estate_property_data.xml @@ -0,0 +1,242 @@ + + + + Beautiful Villa in Downtown + A stunning 3-bedroom villa with modern amenities + 382421 + 2026-06-15 + 6767676.00 + 3 + 9234 + 2 + True + True + 5000 + south + new + + + + Cozy House with Garden + Perfect starter home with large garden + 400605 + 2026-07-01 + 100000.00 + 2 + 500 + 1 + False + True + 2000 + north + new + + + + Modern Apartment + Newly built apartment in city center + 382421 + 2026-05-20 + 250000.00 + 2 + 750 + 3 + True + False + 0 + east + new + + + + Traditional Cottage + Charming countryside cottage + 400605 + 2026-08-10 + 180000.00 + 3 + 650 + 2 + True + True + 3000 + west + new + + + + Luxury Penthouse + High-end penthouse with panoramic views + 382421 + 2026-04-01 + 500000.00 + 4 + 1200 + 2 + True + True + 1000 + south + new + + + + Seaside Bungalow + Relaxing bungalow with sea view + 600001 + 2026-10-01 + 420000.00 + 2 + 800 + 2 + False + True + 1500 + east + new + + + + Mountain Cabin + Cozy cabin in the mountains + 700002 + 2026-11-15 + 210000.00 + 3 + 600 + 1 + False + True + 1200 + west + new + + + + City Studio + Compact studio apartment for singles + 800003 + 2026-12-01 + 95000.00 + 1 + 350 + 1 + False + False + 0 + north + new + + + + Family Home + Spacious home perfect for families + 900004 + 2027-01-10 + 300000.00 + 4 + 1100 + 2 + True + True + 2000 + south + new + + + + Downtown Office Space + Modern office space in business district + 100005 + 2027-02-01 + 800000.00 + 0 + 2000 + 4 + True + False + 0 + east + new + + + + Suburban Ranch + Classic ranch house in quiet suburb + 200006 + 2027-03-15 + 280000.00 + 3 + 950 + 1 + True + True + 2500 + south + new + + + + Historic Listed Building + Charming historic property with character + 300007 + 2027-04-20 + 350000.00 + 4 + 1000 + 3 + False + True + 1800 + west + new + + + + Twin Home Units + Duplex with rental income potential + 400008 + 2027-05-10 + 380000.00 + 5 + 1400 + 2 + True + True + 1600 + north + new + + + + Contemporary Townhouse + Modern townhouse in trendy neighborhood + 500009 + 2027-06-05 + 320000.00 + 3 + 850 + 3 + True + False + 0 + east + new + + + + Lakeside Retreat + Peaceful waterfront property + 600010 + 2027-07-20 + 450000.00 + 3 + 900 + 2 + False + True + 3000 + south + new + + diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..9a2189b6382 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,5 @@ +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..4aec67be3bb --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,119 @@ +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models +from odoo.exceptions import UserError +from odoo.exceptions import ValidationError +from odoo.tools.float_utils import float_compare, float_is_zero + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real estate system" + _order = "id desc" + _inherit = ['mail.thread', 'mail.activity.mixin'] + + _check_expected_price = models.Constraint( + "CHECK(expected_price > 0)", + 'Expected Price must be strictly positive' + ) + _check_selling_price = models.Constraint( + "CHECK(selling_price >= 0)", + 'Selling Price must be positive' + ) + + @api.constrains('selling_price', 'expected_price') + def _check_selling_price(self): + for record in self: + if float_is_zero(record.selling_price, precision_digits=2): + continue + min_price = record.expected_price * 0.90 + if float_compare(record.selling_price, min_price, precision_digits=2) < 0: + raise ValidationError( + "The selling price can't be lower than 90%% of expected price" + ) + + def _get_default_date_calculation(self): + return fields.Date.today() + relativedelta(months=3) + + name = fields.Char(string="Property Name", required=True) + description = fields.Text() + postcode = fields.Char(string="Postal Code") + date_availability = fields.Date(string="Available From", copy=False, default=_get_default_date_calculation) + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer(string="Living Area (sqm)", help="Living area in square meters") + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean(string="Garden", help="Has garden") + garden_area = fields.Integer(string="Garden Area (sqm)", help="Garden area in square meters") + garden_orientation = fields.Selection([ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West') + ]) + active = fields.Boolean(default=True, help="Uncheck to archive this property") + state = fields.Selection([ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], required=True, copy=False, default='new', tracking=True) + property_type_id = fields.Many2one('estate.property.type', string="Property Type", ondelete='cascade') + buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) + seller_id = fields.Many2one('res.users', string="Seller", default=lambda self: self.env.user) + tag_ids = fields.Many2many('estate.property.tag', 'estate_property_tag_rel', 'property_id', 'tag_id', string="Tags") + offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") + total_area = fields.Float(compute='_compute_total_area', store=True) + best_price = fields.Float(compute='_compute_best_price', readonly=True, store=True) + + def action_sold(self): + self.ensure_one() + if self.state == 'cancelled': + raise UserError("Cancelled properties cannot be sold.") + self.state = 'sold' + message = "Successfully Sold the product" + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': message, + 'img_url': '/web/static/img/smile.svg', + 'type': 'rainbow_man', + } + } + return True + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise UserError("Sold properties cannot be cancelled.") + record.state = 'cancelled' + return True + + @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.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price'), default=0.0) + + @api.onchange('garden') + def _onchange_garden(self): + if not self.garden: + self.garden_area = 0 + self.garden_orientation = False + else: + self.garden_area = 10 + self.garden_orientation = 'north' + + @api.ondelete(at_uninstall=False) + def _ondelete_check_state(self): + for records in self: + if records.state not in ('new', 'cancelled'): + raise UserError(f"The property state is in {records.state}, you can't delete this property") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..7ef6c5016c1 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,83 @@ +from datetime import timedelta + +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real estate system - Property Offer" + _order = "price desc" + + _check_offer_price_positive = models.Constraint( + "CHECK(price > 0)", + 'Offer Price must be strictly positive' + ) + + price = fields.Float(string="Offer Price", required=True, copy=False) + status = fields.Selection([ + ('pending', 'Pending'), + ('accepted', 'Accepted'), + ('refused', 'Refused') + ], copy=False, default='pending') + partner_id = fields.Many2one('res.partner', required=True) + property_id = fields.Many2one('estate.property', required=True, ondelete='cascade') + validity = fields.Integer(default=7) + date_deadline = fields.Date( + compute='_compute_date_deadline', + inverse='_inverse_date_deadline' + ) + is_button_hidden = fields.Boolean(compute='_compute_button_visibility', store=False) + property_type_id = fields.Many2one(related='property_id.property_type_id', store=True) + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for offer in self: + date = offer.create_date or fields.Date.today() + offer.date_deadline = date + timedelta(days=offer.validity) + + def _inverse_date_deadline(self): + for record in self: + start = record.create_date.date() if record.create_date else fields.Date.today() + record.validity = (record.date_deadline - start).days + + def action_accept_offer(self): + self.ensure_one() + if self.property_id.state in ('sold', 'cancelled'): + raise UserError("Cannot accept an offer on a sold or cancelled property.") + if self.property_id.offer_ids.filtered(lambda offer: offer.status == 'accepted'): + raise UserError("An offer is already accepted for this property.") + for offer in self.property_id.offer_ids: + if offer.id != self.id: + offer.status = 'refused' + self.status = 'accepted' + self.property_id.selling_price = self.price + self.property_id.buyer_id = self.partner_id.id + self.property_id.state = 'offer_accepted' + return True + + def action_refuse_offer(self): + self.ensure_one() + if self.property_id.state in ('sold', 'cancelled'): + raise UserError("Cannot refuse an offer on a sold or cancelled property.") + if self.status == 'accepted': + raise UserError("Cannot refuse an accepted offer.") + self.status = 'refused' + return True + + @api.model_create_multi + def create(self, vals_list): + for val in vals_list: + prop = self.env['estate.property'].browse(val.get('property_id')) + if val.get('price') < prop.best_price: + raise UserError("The new offer price is less than the existing offered prices") + prop.write({'state': 'offer_received'}) + return super().create(vals_list) + + @api.depends('status', 'property_id.state') + def _compute_button_visibility(self): + for offer in self: + offer.is_button_hidden = ( + offer.status in ('accepted', 'refused') or + offer.property_id.state in ('sold', 'cancelled') + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..3ef805eff61 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,15 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Real estate system - Property Tag" + _order = "name" + + _unique_tag_name = models.Constraint( + "UNIQUE(name)", + 'The Property Tag name has to be Unique.' + ) + + name = fields.Char(string="Tag Name", required=True) + color = fields.Integer(string="Color") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..c7a84116250 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,23 @@ +from odoo import api, fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Real estate system - Property Type" + _order = "sequence, name" + + _check_type_name = models.Constraint( + "UNIQUE(name)", + 'The Property Type name has to be Unique.' + ) + + name = fields.Char(string="Property Type Name", required=True) + property_ids = fields.One2many('estate.property', 'property_type_id', string="Properties") + sequence = fields.Integer(default=10) + offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string="Offers") + offer_count = fields.Integer(string="Number of Offers", compute='_compute_offer_count') + + @api.depends('offer_ids') + def _compute_offer_count(self): + for record in self: + record.offer_count = len(record.offer_ids) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..f826a0c633b --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = 'res.users' + + seller_property_ids = fields.One2many( + 'estate.property', + 'seller_id', + string="Real Estate Properties", + domain=[('state', 'not in', ('sold', 'cancelled'))] + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..6905974b938 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,9 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property_manager,estate.property manager,model_estate_property,estate.estate_group_manager,1,1,1,1 +access_estate_property_user,estate.property user,model_estate_property,estate.estate_group_user,1,1,0,0 +access_estate_property_type_manager,estate.property.type manager,model_estate_property_type,estate.estate_group_manager,1,1,1,1 +access_estate_property_type_user,estate.property.type user,model_estate_property_type,estate.estate_group_user,1,0,0,0 +access_estate_property_tag_manager,estate.property.tag manager,model_estate_property_tag,estate.estate_group_manager,1,1,1,1 +access_estate_property_tag_user,estate.property.tag user,model_estate_property_tag,estate.estate_group_user,1,0,0,0 +access_estate_property_offer_manager,estate.property.offer manager,model_estate_property_offer,estate.estate_group_manager,1,1,1,1 +access_estate_property_offer_user,estate.property.offer user,model_estate_property_offer,estate.estate_group_user,1,1,1,0 diff --git a/estate/security/security.xml b/estate/security/security.xml new file mode 100644 index 00000000000..61bad72d4f7 --- /dev/null +++ b/estate/security/security.xml @@ -0,0 +1,29 @@ + + + + + Real Estate + Real Estate Management + + + + Real Estate + + + + + Agent + + + + + Manager + + + + + + + + + diff --git a/estate/static/description/icon.png b/estate/static/description/icon.png new file mode 100644 index 00000000000..6fc6561a21c Binary files /dev/null and b/estate/static/description/icon.png differ diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..53f245deb28 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml new file mode 100644 index 00000000000..4846f83c70f --- /dev/null +++ b/estate/views/estate_property_offers_views.xml @@ -0,0 +1,60 @@ + + + + Offers + estate.property.offer + list,form + [('property_type_id', '=', active_id)] + {'default_property_type_id': active_id} + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + + + +
+
+
+ + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + estate.property.type.list + estate.property.type + + + + + + + + + + + estate.property.type.search + estate.property.type + + + + + + + + + Property Types + estate.property.type + list,form + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..8b1ae9bc885 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,173 @@ + + + + estate.property.form + estate.property + +
+
+
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + estate.property.list + estate.property + + + + + + + + + + + + + + + + + + + estate.property.kanban + estate.property + + + + + +
+ +
Expected Price:
+
+ Best Price: +
+
+ Selling Price: +
+
+ +
+
+
+
+
+
+
+ + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + + + + + + Properties + estate.property + list,kanban,form + estate + {'search_default_properties_with_offers': 1} + +
diff --git a/estate/views/estate_res_users_view.xml b/estate/views/estate_res_users_view.xml new file mode 100644 index 00000000000..362702fc873 --- /dev/null +++ b/estate/views/estate_res_users_view.xml @@ -0,0 +1,29 @@ + + + + res.users.form.inherit.estate + res.users + + + + + + + + + + + + + + + + + + + + + diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..2c4b36be1d3 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,8 @@ +{ + 'name': 'RealEstateAccount', + 'version': '1.0', + 'author': 'Pranjali Sangavekar(prsan)', + 'license': 'LGPL-3', + 'depends': ['estate', 'account'], + 'application': True, +} 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..65121dfa113 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,25 @@ +from odoo import Command, models + + +class InheritedEstateProperty(models.Model): + _inherit = 'estate.property' + + def action_sold(self): + for record in self: + self.env['account.move'].create({ + 'partner_id': record.buyer_id.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': [ + Command.create({ + 'name': f'{record.name} - Commission (6%)', + 'quantity': 1, + 'price_unit': record.selling_price * 0.06, + }), + Command.create({ + 'name': f'{record.name} - Administrative Fees', + 'quantity': 1, + 'price_unit': 100.00, + }), + ], + }) + return super().action_sold() diff --git a/estate_auction/__init__.py b/estate_auction/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_auction/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_auction/__manifest__.py b/estate_auction/__manifest__.py new file mode 100644 index 00000000000..78afe1f387b --- /dev/null +++ b/estate_auction/__manifest__.py @@ -0,0 +1,22 @@ +{ + 'name': 'RealEstateAuction', + 'version': '1.0', + 'depends': ['estate', 'web', 'estate_website_settings'], + 'author': 'Pranjali Sangavekar(prsan)', + 'license': 'LGPL-3', + 'application': True, + + 'data': [ + 'views/estate_auction_view.xml', + 'views/website_property_detail_inherit.xml', + 'data/ir_cron_estate.xml' + ], + + 'assets': { + 'web.assets_backend': [ + 'estate_auction/static/src/components/auction_state_selection/auction_state_selection.js', + 'estate_auction/static/src/components/auction_state_selection/auction_state_selection.xml', + 'estate_auction/static/src/components/auction_state_selection/auction_state_selection.scss', + ], + }, +} diff --git a/estate_auction/data/ir_cron_estate.xml b/estate_auction/data/ir_cron_estate.xml new file mode 100644 index 00000000000..61b49f22cef --- /dev/null +++ b/estate_auction/data/ir_cron_estate.xml @@ -0,0 +1,12 @@ + + + + Close Expired Auctions + + code + model._cron_close_expired_auctions() + 1 + minutes + True + + \ No newline at end of file diff --git a/estate_auction/models/__init__.py b/estate_auction/models/__init__.py new file mode 100644 index 00000000000..f1e9fb549dc --- /dev/null +++ b/estate_auction/models/__init__.py @@ -0,0 +1,2 @@ +from . import estate_auction +from . import estate_auction_offers diff --git a/estate_auction/models/estate_auction.py b/estate_auction/models/estate_auction.py new file mode 100644 index 00000000000..1366d6afd67 --- /dev/null +++ b/estate_auction/models/estate_auction.py @@ -0,0 +1,44 @@ +from odoo import api, fields, models + + +class EstateProperty(models.Model): + _inherit = 'estate.property' + + sale_mode = fields.Selection([ + ('regular', 'Regular'), + ('auction', 'Auction'), + ], default='regular', required=True) + auction_end_time = fields.Datetime(string="End Time") + highest_offer = fields.Float(string="Highest Offer", readonly=True) + highest_bidder_id = fields.Many2one('res.partner', string="Highest Bidder", readonly=True) + auction_state = fields.Selection([ + ('template', 'Template'), + ('auction', 'Auction'), + ('sold', 'Sold'), + ], default='template', string="Auction State", store=True, tracking=True) + + def action_start_auction(self): + self.ensure_one() + self.auction_state = 'auction' + + @api.model + def _cron_close_expired_auctions(self): + properties = self.search([ + ('sale_mode', '=', 'auction'), + ('auction_state', '=', 'auction'), + ('auction_end_time', '<=', fields.Datetime.now()), + ]) + + for property in properties: + highest_offer = property.offer_ids.filtered(lambda o: o.status == 'pending')[:1] + + if not highest_offer: + continue + + highest_offer.action_accept_offer() + property.write({ + 'highest_offer': highest_offer.price, + 'highest_bidder_id': highest_offer.partner_id.id, + 'auction_state': 'sold', + 'state': 'sold' + }) diff --git a/estate_auction/models/estate_auction_offers.py b/estate_auction/models/estate_auction_offers.py new file mode 100644 index 00000000000..6cb2643ffaa --- /dev/null +++ b/estate_auction/models/estate_auction_offers.py @@ -0,0 +1,32 @@ +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class EstateAuctionOffer(models.Model): + _inherit = 'estate.property.offer' + + is_button_hidden = fields.Boolean(compute='_compute_button_visibility', store=False) + + @api.depends('property_id.sale_mode') + def _compute_button_visibility(self): + for offer in self: + offer.is_button_hidden = ( + offer.property_id.sale_mode == 'auction' + ) + + @api.model_create_multi + def create(self, vals_list): + for val in vals_list: + prop = self.env['estate.property'].browse(val.get('property_id')) + price = val.get('price', 0) + + if prop.sale_mode == 'auction': + if price < prop.expected_price: + raise UserError( + "Auction offer cannot be lower than the expected price." + ) + else: + return super().create(vals_list) + + prop.write({'state': 'offer_received'}) + return models.Model.create(self, vals_list) diff --git a/estate_auction/static/src/components/auction_state_selection/auction_state_selection.js b/estate_auction/static/src/components/auction_state_selection/auction_state_selection.js new file mode 100644 index 00000000000..d66e2c944c5 --- /dev/null +++ b/estate_auction/static/src/components/auction_state_selection/auction_state_selection.js @@ -0,0 +1,55 @@ +import { + StateSelectionField, + stateSelectionField, +} from "@web/views/fields/state_selection/state_selection_field"; + +import { registry } from "@web/core/registry"; + + +export class AuctionStateSelection extends StateSelectionField { + static template = "estate_auction.AuctionStateSelection"; + + setup() { + super.setup(); + + this.icons = { + "template": "fa fa-circle-o", + "auction": "fa fa-check-circle", + "sold": "fa fa-times-circle", + }; + + this.colorIcons = { + "template": "text-secondary", + "auction": "text-success", + "sold": "text-danger", + }; + + this.colorButton = { + "template": "btn-outline-secondary", + "auction": "btn-outline-success", + "sold": "btn-outline-danger", + }; + } + + stateIcon(value) { + return this.icons[value] || ""; + } + + statusColor(value) { + return this.colorIcons[value] || ""; + } + + getTogglerClass(currentValue) { + return ( + "o_state_button btn rounded-pill " + + this.colorButton[currentValue] + ); + } +} + +export const auctionStateSelection = { + ...stateSelectionField, + component: AuctionStateSelection, +}; + +registry.category("fields").add("auction_state_selection", auctionStateSelection); diff --git a/estate_auction/static/src/components/auction_state_selection/auction_state_selection.scss b/estate_auction/static/src/components/auction_state_selection/auction_state_selection.scss new file mode 100644 index 00000000000..87e0184586f --- /dev/null +++ b/estate_auction/static/src/components/auction_state_selection/auction_state_selection.scss @@ -0,0 +1,20 @@ +.o_state_button { + min-width: 140px; + justify-content: center; +} + +.o_state_button .o_status_label { + white-space: nowrap; +} + +.text-secondary { + color: #6c757d !important; +} + +.text-success { + color: #28a745 !important; +} + +.text-danger { + color: #dc3545 !important; +} diff --git a/estate_auction/static/src/components/auction_state_selection/auction_state_selection.xml b/estate_auction/static/src/components/auction_state_selection/auction_state_selection.xml new file mode 100644 index 00000000000..f363a314f41 --- /dev/null +++ b/estate_auction/static/src/components/auction_state_selection/auction_state_selection.xml @@ -0,0 +1,42 @@ + + + + + + + + + + getTogglerClass(currentValue) + + + + + +
+ + + Auction Stages + +
+
+ + + + + {{ stateIcon(option[0]) }} + + + + + + + {{ statusColor(option[0]) }} + + + +
+ +
diff --git a/estate_auction/views/estate_auction_view.xml b/estate_auction/views/estate_auction_view.xml new file mode 100644 index 00000000000..5024c722c6f --- /dev/null +++ b/estate_auction/views/estate_auction_view.xml @@ -0,0 +1,49 @@ + + + + estate.property.form.auction + estate.property + + + + +