From cbb59ff3dfc880809974a7b560b1e27c7273aa8c Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Fri, 22 Nov 2024 18:41:28 +0530 Subject: [PATCH 01/16] [ADD] estate: Added new models related to property model Learned about how to make our Application and model and all things around that basic and complex fields, things related to security fields, how to make UI changes, how to create relationships between models and fields, and things related to computed fields and onchange methods. --- estate/__init__.py | 1 + estate/__manifest__.py | 14 +++ estate/models/__init__.py | 4 + estate/models/estate_property.py | 97 +++++++++++++++ estate/models/estate_property_offer.py | 40 ++++++ estate/models/estate_property_tag.py | 11 ++ estate/models/estate_property_type.py | 7 ++ estate/security/ir.model.access.csv | 6 + estate/views/estate_menus.xml | 18 +++ estate/views/estate_property_type_views.xml | 27 ++++ estate/views/estate_property_views.xml | 131 ++++++++++++++++++++ 11 files changed, 356 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_type_views.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..ff5c2f635ce --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,14 @@ +{ + 'name': 'estate', + 'version': '1.0', + 'depends': [ + 'base', + ], + 'installable': True, + 'application': True, + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', + ] +} \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..09b2099fe84 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,4 @@ +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..bc88bd399e2 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,97 @@ +from odoo.fields import float_compare, float_is_zero +from odoo import fields, models, api +from odoo.exceptions import UserError, ValidationError + +class estate_property(models.Model): + _name = "estate.property" + _description = "real estate property" + name = fields.Char(required=True) + active = fields.Boolean(default=True) + 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(required=True) + selling_price = fields.Float(readonly=True,copy=False,default=0.0) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + state = fields.Selection( + selection=[('New', 'New'), ('Offer Received', 'Offer Received'), ('Offer Accepted', 'Offer Accepted'), ('Sold', 'Sold'), ('Cancelled', 'Cancelled')], + copy=False, + required=True, + default='New') + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]) + salesman = fields.Many2one("res.users", string='Salesperson', default=lambda self: self.env.user) + buyer = fields.Many2one("res.partner", copy=False) + property_type_id = fields.Many2one("estate.property.type") + tag_ids = fields.Many2many("estate.property.tag") + offer_ids = fields.One2many(comodel_name="estate.property.offer", inverse_name="property_id", string= "Offers") + total_area = fields.Integer(compute="_compute_total_area") + best_offer = fields.Float(compute="_compute_best_offer") + + _sql_constraints = [ + ('check_expected_price', 'CHECK(expected_price >= 0 AND selling_price >= 0 AND offer_ids.price > 0)', + 'The expected price and selling price should be greater than equal to zero.') + ] + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for area in self: + area.total_area = area.living_area + area.garden_area + + @api.depends('offer_ids') + def _compute_best_offer(self): + for record in self: + all_sequences = record.offer_ids.mapped('price') + record.best_offer = max(all_sequences) if all_sequences else 0.0 + + @api.onchange("garden") + def _onchange_garden(self): + for record in self: + if record.garden: + record.garden_area = 1000 + record.garden_orientation = "north" + else: + record.garden_area = 0 + record.garden_orientation = "" + + + def action_set_property_as_cancle(self): + if(self.state == 'Sold'): + raise UserError(("Sold property can't be cancelled")) + else: + self.state = 'Cancelled' + + def action_set_property_as_sold(self): + if(self.state == 'Cancelled'): + raise UserError(("Cancelled property can't be sold")) + else: + self.state = 'Sold' + + def action_accept_offer(self): + # Logic to accept the offer for the property + for offer in self.offer_ids: + offer.action_accept_offer() + return True + + def action_refuse_offer(self): + # Logic to refuse the offer for the property + for offer in self.offer_ids: + offer.action_refuse_offer() + return True + + @api.constrains('selling_price', 'expected_price') + def _check_selling_price(self): + for record in self: + if float_is_zero(record.selling_price, precision_rounding=record.env.company.currency_id.rounding): + continue + min_price = 0.9 * record.expected_price + if float_compare(record.selling_price, min_price, precision_rounding=record.env.company.currency_id.rounding) < 0: + raise ValidationError( + _("The selling price (%.2f) cannot be lower than 90%% of the expected price (%.2f).") + % (record.selling_price, min_price) + ) \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..7c28dee056e --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,40 @@ +from odoo import fields, models, api + +class estate_property_offer(models.Model): + _name = "estate.property.offer" + _description = "real estate property offers" + + price = fields.Float() + status = fields.Selection( + selection=[('accepted', 'Accepted'), ('refused', 'Refused')], + copy=False) + partner_id = fields.Many2one("res.partner", required = True) + property_id = fields.Many2one("estate.property", required = True) + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") + + _sql_constraints = [ + ('check_price', 'CHECK(price > 0)', + 'The Offer price should be greater than equal to zero.') + ] + + @api.depends("validity") + def _compute_date_deadline(self): + for record in self: + record.date_deadline = (fields.Date.add(fields.Date.today(), days=+record.validity)) + + def _inverse_date_deadline(self): + for record in self: + record.validity = fields.Date.add(fields.Date.today(), days=+record.validity) + + def action_accept_offer(self): + for record in self: + record.status = 'accepted' + record.property_id.buyer = record.partner_id + record.property_id.selling_price = record.price + return True + + def action_refuse_offer(self): + for record in self: + record.status = 'refused' + return True \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..531ec5f64ed --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,11 @@ +from odoo import models, fields # type: ignore + +class estate_property_tag(models.Model): + _name = "estate.property.tag" + _description = "real estate property tags" + + name = fields.Char(required=True) + + _sql_constraints = [ + ('code_tag_uniq', 'unique (name)', 'Tag name must be unique.'), + ] \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..02fe099421b --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class estate_property_type(models.Model): + _name = "estate.property.type" + _description = "real estate property types" + property_type = fields.Text() + name = fields.Char(required=True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..f04c59826ce --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate__property_type,access_estate__property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate__property_tag,access_estate__property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate__property_offer,access_estate__property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ 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..bd194399cff --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> + +<odoo> + <menuitem id="real_estate_model" name="Real Estate"> + <menuitem id="first_level_menu1" name="Advertisements"> + <menuitem id="property_model_menu_action" name="properities" + action="estate_action_second" /> + </menuitem> + + <menuitem id="first_level_menu2" name="Settings"> + <menuitem id="property_type_model_menu_action" name="Property Types" + action="property_type_action" /> + + <menuitem id="property_tag_model_menu_action" name="Property Tags" + action="property_tag_action" /> + </menuitem> + </menuitem> +</odoo> \ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..3b7d43ed29d --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> + +<odoo> + <record id="estate_property_view_form" model="ir.ui.view"> + <field name="name">estate.property.type.form</field> + <field name="model">estate.property.type</field> + <field name="arch" type="xml"> + <form string="Estate Property Type"> + <sheet> + <h1 class="mb32"> + <field name="property_type" class="mb16" /> + </h1> + </sheet> + </form> + </field> + </record> + + <record id="estate_property_type_tree_view" model="ir.ui.view"> + <field name="name">estate.property.type.tree</field> + <field name="model">estate.property.type</field> + <field name="arch" type="xml"> + <list string="Channel"> + <field name="property_type" string="Property Type" /> + </list> + </field> + </record> +</odoo> \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..15fc7c9dc68 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,131 @@ +<?xml version="1.0"?> + +<odoo> + <record id="estate_action_second" model="ir.actions.act_window"> + <field name="name">estate.property.action</field> + <field name="res_model">estate.property</field> + <field name="view_mode">list,form</field> + </record> + + <record id="property_type_action" model="ir.actions.act_window"> + <field name="name">estate.property.type.action</field> + <field name="res_model">estate.property.type</field> + <field name="view_mode">list,form</field> + </record> + + <record id="property_tag_action" model="ir.actions.act_window"> + <field name="name">estate.property.tag.action</field> + <field name="res_model">estate.property.tag</field> + <field name="view_mode">list,form</field> + </record> + + <record id="estate_property_tree_view" model="ir.ui.view"> + <field name="name">estate.property.tree</field> + <field name="model">estate.property</field> + <field name="arch" type="xml"> + <list string="Channel"> + <field name="name" string="Title" /> + <field name="postcode" /> + <field name="bedrooms" /> + <field name="living_area" string="Living Area (sqm)" /> + <field name="expected_price" /> + <field name="best_offer" /> + <field name="selling_price" /> + <field name="date_availability" /> + </list> + </field> + </record> + + <record id="estate_property_view_form" model="ir.ui.view"> + <field name="name">estate.property.form</field> + <field name="model">estate.property</field> + <field name="arch" type="xml"> + <form string="Estate Property"> + <header> + <button name="action_set_property_as_sold" string="SOLD" + type="object" /> + <button name="action_set_property_as_cancle" string="CANCLE" + type="object" /> + </header> + <sheet> + <h1 class="mb32"> + <field name="name" class="mb16" /> + </h1> + <field name="tag_ids" widget="many2many_tags" /> + <group> + <group> + <field name="state" /> + <field name="postcode" /> + <field string="Available From" name="date_availability" /> + </group> + <group>estate.property.tag <field name="expected_price" /> + <field + name="best_offer" /> + <field name="selling_price" /> + </group> + </group> + <notebook> + <page string="Description"> + <group> + <field name="description" class="mb16" /> + <field name="bedrooms" class="mb16" /> + <field string="Living Area (sqm)" name="living_area" class="mb16" /> + <field name="facades" class="mb16" /> + <field name="garage" class="mb16" /> + <field name="garden" class="mb16" /> + <field string="Garden Area (sqm)" name="garden_area" class="mb16" /> + <field name="garden_orientation" class="mb16" /> + <field string="Total Area (sqm)" name="total_area" class="mb16" /> + </group> + </page> + + <page string="Offers"> + <group> + <field name="offer_ids" class="mb16"> + <list> + <field name="price" /> + <field name="property_id" /> + <field name="status" /> + <field name="validity" /> + <field name="date_deadline" string="Deadline" /> + <button name="action_accept_offer" + type="object" icon="fa-check" /> + <button name="action_refuse_offer" + type="object" icon="fa-times" /> + </list> + </field> + </group> + </page> + + <page string="Other Info"> + <group> + <field name="salesman" class="mb16" /> + <field name="buyer" class="mb16" /> + </group> + </page> + </notebook> + </sheet> + </form> + </field> + </record> + + <record id="estate_property_search" model="ir.ui.view"> + <field name="name">estate.property.search</field> + <field name="model">estate.property</field> + <field name="arch" type="xml"> + <search string=""> + <field name="name" string="Title" /> + <field name="postcode" string="Postcode" /> + <field name="expected_price" string="Expected Price" /> + <field name="bedrooms" string="Bedrooms" /> + <field name="living_area" string="Living Area (sqm)" /> + <field name="facades" string="Facades" /> + <filter string="Property State" name="state" + domain="[('state', 'in', ('New', 'Offer Received'))]" /> + <group expand="1" string="Group By"> + <filter string="Postcode" name="postcode" context="{'group_by':'postcode'}" /> + </group> + </search> + </field> + </record> +</odoo> \ No newline at end of file From fae129ec613cf849a800a619d263d3e6da9f2d55 Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Tue, 26 Nov 2024 18:38:46 +0530 Subject: [PATCH 02/16] [ADD] estate: done UI changes and added new model Added changes for UI for validation around if someone offers less price than previous offer and some UI fields should be visible on some conditions that changes are added and also added color fields to tags to differentiate added state button to directly go to all offers related to that property type for ease of use added SQL constraints and Python-level constraints to check for validations of the code and for all these changes added related to code to views. --- estate/models/estate_property.py | 27 ++++++-- estate/models/estate_property_offer.py | 31 +++++++-- estate/models/estate_property_tag.py | 2 + estate/models/estate_property_type.py | 18 ++++- estate/models/res_users.py | 6 ++ estate/views/estate_property_views.xml | 94 ++++++++++++++++++++++---- 6 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 estate/models/res_users.py diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index bc88bd399e2..e35a7eaa8b9 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,6 +5,8 @@ class estate_property(models.Model): _name = "estate.property" _description = "real estate property" + _order = "id desc" + name = fields.Char(required=True) active = fields.Boolean(default=True) description = fields.Text() @@ -33,9 +35,10 @@ class estate_property(models.Model): total_area = fields.Integer(compute="_compute_total_area") best_offer = fields.Float(compute="_compute_best_offer") + sequence = fields.Integer(string="Sequence", default=1) + _sql_constraints = [ - ('check_expected_price', 'CHECK(expected_price >= 0 AND selling_price >= 0 AND offer_ids.price > 0)', - 'The expected price and selling price should be greater than equal to zero.') + ('check_expected_price', 'CHECK(expected_price >= 0 AND selling_price >= 0)', 'The expected price and selling price should be greater than equal to zero.') ] @api.depends('living_area', 'garden_area') @@ -87,11 +90,23 @@ def action_refuse_offer(self): @api.constrains('selling_price', 'expected_price') def _check_selling_price(self): for record in self: - if float_is_zero(record.selling_price, precision_rounding=record.env.company.currency_id.rounding): + if float_is_zero(record.selling_price, precision_rounding=2): continue min_price = 0.9 * record.expected_price - if float_compare(record.selling_price, min_price, precision_rounding=record.env.company.currency_id.rounding) < 0: + print("min price : ", min_price) + if float_compare(record.selling_price, min_price, precision_rounding=2) < 0: raise ValidationError( - _("The selling price (%.2f) cannot be lower than 90%% of the expected price (%.2f).") + ("The selling price (%.2f) cannot be lower than 90%% of the expected price (%.2f).") % (record.selling_price, min_price) - ) \ No newline at end of file + ) + + + @api.ondelete(at_uninstall=False) + def _check_deletion_state(self): + for record in self: + if record.state not in ['New', 'Cancelled']: + raise UserError("You cannot delete a property unless its state is 'New' or 'Cancelled'.") + + + + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7c28dee056e..b36c8556bb9 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,17 +1,24 @@ from odoo import fields, models, api +from odoo.exceptions import UserError + class estate_property_offer(models.Model): _name = "estate.property.offer" _description = "real estate property offers" + _order = "price desc" price = fields.Float() status = fields.Selection( selection=[('accepted', 'Accepted'), ('refused', 'Refused')], copy=False) - partner_id = fields.Many2one("res.partner", required = True) + partner_id = fields.Many2one("res.partner", string="Partner") property_id = fields.Many2one("estate.property", required = True) validity = fields.Integer(default=7) date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") + property_type_id = fields.Many2one(comodel_name="estate.property.type", + string="Property Type", + related="property_id.property_type_id", + store=True) _sql_constraints = [ ('check_price', 'CHECK(price > 0)', @@ -21,11 +28,15 @@ class estate_property_offer(models.Model): @api.depends("validity") def _compute_date_deadline(self): for record in self: - record.date_deadline = (fields.Date.add(fields.Date.today(), days=+record.validity)) + if record.validity: + record.date_deadline = fields.Date.add(fields.Date.today(), days=record.validity) def _inverse_date_deadline(self): for record in self: - record.validity = fields.Date.add(fields.Date.today(), days=+record.validity) + if record.date_deadline: + record.validity = (record.date_deadline - fields.Date.today()).days + else: + record.validity = 0 def action_accept_offer(self): for record in self: @@ -37,4 +48,16 @@ def action_accept_offer(self): def action_refuse_offer(self): for record in self: record.status = 'refused' - return True \ No newline at end of file + return True + + + @api.model + def create(self, vals): + property_record = self.env['estate.property'].browse(vals['property_id']) + + if property_record.offer_ids.filtered(lambda o: o.price >= vals['price']): + raise UserError("You cannot create an offer lower than an existing offer.") + + property_record.state = 'Offer Received' + + return super(estate_property_offer, self).create(vals) \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 531ec5f64ed..e7d55924ed9 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -3,8 +3,10 @@ class estate_property_tag(models.Model): _name = "estate.property.tag" _description = "real estate property tags" + _order = "name" name = fields.Char(required=True) + color = fields.Integer(string="color") _sql_constraints = [ ('code_tag_uniq', 'unique (name)', 'Tag name must be unique.'), diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 02fe099421b..e4a6ecddf2f 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,7 +1,21 @@ -from odoo import fields, models +from odoo import fields, models, api class estate_property_type(models.Model): _name = "estate.property.type" _description = "real estate property types" + _order = "sequence, name" + property_type = fields.Text() - name = fields.Char(required=True) \ No newline at end of file + name = fields.Char() + sequence = fields.Integer(string='Sequence', default=1) + + property_ids = fields.One2many(comodel_name="estate.property", inverse_name="property_type_id") + offer_ids = fields.One2many(comodel_name="estate.property.offer", inverse_name="property_type_id") + offer_count = fields.Integer(string="Offer Count", + compute="_compute_offer_count", + store=True) + + @api.depends('offer_ids') + def _compute_offer_count(self): + for record in self: + record.offer_count = len(record.offer_ids) \ No newline at end of file diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..e935849f575 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,6 @@ +from odoo import fields, models + +class users_inherited(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many("estate.property") \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 15fc7c9dc68..ddc205d960b 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -19,6 +19,27 @@ <field name="view_mode">list,form</field> </record> + <record id="estate_property_offer_action" model="ir.actions.act_window"> + <field name="name">Offers</field> + <field name="res_model">estate.property.offer</field> + <field name="view_mode">list,form</field> + <field name="domain">[('property_type_id', '=', active_id)]</field> + </record> + + + <record id="estate_property_offer_tree_view" model="ir.ui.view"> + <field name="name">estate.property.offer.tree</field> + <field name="model">estate.property.offer</field> + <field name="arch" type="xml"> + <list> + <field name="property_id" /> + <field name="property_type_id" /> + <field name="price" /> + </list> + </field> + </record> + + <record id="estate_property_tree_view" model="ir.ui.view"> <field name="name">estate.property.tree</field> <field name="model">estate.property</field> @@ -31,11 +52,12 @@ <field name="expected_price" /> <field name="best_offer" /> <field name="selling_price" /> - <field name="date_availability" /> + <field name="date_availability" optional="hide" /> </list> </field> </record> + <record id="estate_property_view_form" model="ir.ui.view"> <field name="name">estate.property.form</field> <field name="model">estate.property</field> @@ -43,15 +65,19 @@ <form string="Estate Property"> <header> <button name="action_set_property_as_sold" string="SOLD" - type="object" /> + type="object" invisible="state != 'Offer Accepted'" /> <button name="action_set_property_as_cancle" string="CANCLE" - type="object" /> + type="object" + invisible="state in ['Cancelled', 'Sold']" /> + <field name="state" widget="statusbar" + statusbar_visible="New,Offer Received,Offer Accepted,Sold" /> </header> + <sheet> <h1 class="mb32"> <field name="name" class="mb16" /> </h1> - <field name="tag_ids" widget="many2many_tags" /> + <field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" /> <group> <group> <field name="state" /> @@ -71,27 +97,33 @@ <field name="bedrooms" class="mb16" /> <field string="Living Area (sqm)" name="living_area" class="mb16" /> <field name="facades" class="mb16" /> - <field name="garage" class="mb16" /> + <field name="garage" class="Add the field in the estate.pmb16" /> <field name="garden" class="mb16" /> - <field string="Garden Area (sqm)" name="garden_area" class="mb16" /> - <field name="garden_orientation" class="mb16" /> + <field string="Garden Area (sqm)" name="garden_area" class="mb16" + invisible="not garden" /> + <field name="garden_orientation" class="mb16" invisible="not garden" /> <field string="Total Area (sqm)" name="total_area" class="mb16" /> </group> </page> <page string="Offers"> <group> - <field name="offer_ids" class="mb16"> - <list> + <field name="offer_ids" class="mb16" + readonly="state in ['Offer Accepted', 'Sold', 'Cancelled']"> + <list editable="bottom" + decoration-success="status == 'accepted'" + decoration-danger="status == 'refused'"> <field name="price" /> <field name="property_id" /> <field name="status" /> <field name="validity" /> <field name="date_deadline" string="Deadline" /> <button name="action_accept_offer" - type="object" icon="fa-check" /> + type="object" icon="fa-check" + invisible="status in ['accepted', 'refused']" /> <button name="action_refuse_offer" - type="object" icon="fa-times" /> + type="object" icon="fa-times" + invisible="status in ['accepted', 'refused']" /> </list> </field> </group> @@ -109,11 +141,46 @@ </field> </record> + <record id="estate_property_type_view_form" model="ir.ui.view"> + <field name="name">estate.property.type.form</field> + <field name="model">estate.property.type</field> + <field name="arch" type="xml"> + <form string="Estate Property"> + <header> + <button + name="%(estate_property_offer_action)d" + type="action" + string="Offers" + icon="fa-list" /> + </header> + <sheet> + <h1 class="mb32"> + <field name="name" class="mb16" /> + </h1> + <notebook> + <page string="Properties" create="false"> + <field name="property_ids" create="false" edit="false" + options=" {'no_create' : True}"> + <list> + <field name="sequence" widget="handle" /> + <field name="name" string="Title" /> + <field name="expected_price" string="Expected Price" /> + <field name="state" string="Status" /> + </list> + </field> + </page> + </notebook> + </sheet> + </form> + </field> + </record> + + <record id="estate_property_search" model="ir.ui.view"> <field name="name">estate.property.search</field> <field name="model">estate.property</field> <field name="arch" type="xml"> - <search string=""> + <search string="Property Search"> <field name="name" string="Title" /> <field name="postcode" string="Postcode" /> <field name="expected_price" string="Expected Price" /> @@ -122,6 +189,9 @@ <field name="facades" string="Facades" /> <filter string="Property State" name="state" domain="[('state', 'in', ('New', 'Offer Received'))]" /> + + <field name="living_area" string="Living Area (sqm)" + filter_domain="[('living_area', '>=', self)]" /> <group expand="1" string="Group By"> <filter string="Postcode" name="postcode" context="{'group_by':'postcode'}" /> </group> From c389796fd365989364c93a49b82c1ad840e20d40 Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Wed, 27 Nov 2024 18:23:34 +0530 Subject: [PATCH 03/16] [ADD] estate: added new res.user model and estate account model Added code for new res. user model and added code for the account model to create an invoice when any estate property is sold. I added code for the Kanban view for easy movement properties. In the invoice, I added a code for the commission fee and some extra charges for the selling price. --- estate/models/res_users.py | 7 +- estate/views/estate_property_views.xml | 75 ++++++++++++++++++++- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 16 +++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 36 ++++++++++ estate_account/security/ir.model.access.csv | 3 + 7 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py create mode 100644 estate_account/security/ir.model.access.csv diff --git a/estate/models/res_users.py b/estate/models/res_users.py index e935849f575..781c1a73a24 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -3,4 +3,9 @@ class users_inherited(models.Model): _inherit = "res.users" - property_ids = fields.One2many("estate.property") \ No newline at end of file + property_ids = fields.One2many( + comodel_name="estate.property", + inverse_name="salesman", + string="Properties", + domain="[('state', 'in', ['New', 'Offer Received'])]" + ) \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index ddc205d960b..0c667f840eb 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,7 +4,7 @@ <record id="estate_action_second" model="ir.actions.act_window"> <field name="name">estate.property.action</field> <field name="res_model">estate.property</field> - <field name="view_mode">list,form</field> + <field name="view_mode">list,form,kanban</field> </record> <record id="property_type_action" model="ir.actions.act_window"> @@ -39,6 +39,56 @@ </field> </record> + <record id="estate_property_kanban_view" model="ir.ui.view"> + <field name="name">estate.property.kanban</field> + <field name="model">estate.property</field> + <field name="arch" type="xml"> + <kanban default_group_by="property_type_id"> + <field name="property_type_id" /> + <templates> + <t t-name="kanban-box"> + <div t-att-class="'oe_kanban_global_click oe_kanban_card'"> + <div class="o_kanban_record_top"> + <div class="o_kanban_primary"> + <strong> + <field name="name" /> + </strong> + </div> + </div> + + <div class="o_kanban_details"> + <div> + <strong>Expected Price:</strong> + <field name="expected_price" widget="monetary" + options="{'currency_field': 'currency_id'}" /> + </div> + + <div + t-if="record.state and record.state.raw_value == 'Offer Received'"> + <strong>Best Offer:</strong> + <field name="best_offer" widget="monetary" + options="{'currency_field': 'currency_id'}" /> + </div> + + <div + t-if="record.state and record.state.raw_value == 'Offer Accepted'"> + <strong>Selling Price:</strong> + <field name="selling_price" widget="monetary" + options="{'currency_field': 'currency_id'}" /> + </div> + + <div> + <field name="tag_ids" widget="many2many_tags" + options="{'color_field': 'color'}" /> + </div> + </div> + </div> + </t> + </templates> + </kanban> + </field> + </record> + <record id="estate_property_tree_view" model="ir.ui.view"> <field name="name">estate.property.tree</field> @@ -84,7 +134,8 @@ <field name="postcode" /> <field string="Available From" name="date_availability" /> </group> - <group>estate.property.tag <field name="expected_price" /> + <group>estate.property.tag <field + name="expected_price" /> <field name="best_offer" /> <field name="selling_price" /> @@ -175,6 +226,26 @@ </field> </record> + <!-- <record id="view_users_form_inherit_properties" model="ir.ui.view"> + <field name="name">res.users.form.inherit.properties</field> + <field name="model">res.users</field> + <field name="inherit_id" ref="base.view_users_form" /> + <field name="arch" type="xml"> + <xpath expr="//notebook" position="inside"> + <page string="Properties"> + <field name="property_ids" + context="{'form_view_ref': 'estate.estate_property_view_form'}"> + <tree> + <field name="name" /> + <field name="state" /> + <field name="expected_price" /> + </tree> + </field> + </page> + </xpath> + </field> + </record> --> + <record id="estate_property_search" model="ir.ui.view"> <field name="name">estate.property.search</field> 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..93f77f71ade --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,16 @@ +{ + 'name': 'Estate Account', + 'version': '1.0', + 'category': 'Accounting', + 'depends': ['base', 'estate', 'account'], + 'data': [], + 'installable': True, + 'application': False, + 'auto_install': False, + 'summary': 'Integration between Estate and Accounting modules.', + 'description': """ + Estate Account Module + ===================== + This module integrates the Estate module with the Account module. + """, +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..828e4cc8ac3 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,36 @@ + +from odoo import models + +class estate_property(models.Model): + _inherit = 'estate.property' + + def action_set_property_as_sold(self): + for record in self: + if not record.buyer: + raise ValueError(("Please set a buyer before selling the property.")) + + if not record.selling_price: + raise ValueError(("Please set a selling price before selling the property.")) + + commission_amount = record.selling_price * 0.06 + admin_fee = 100.00 + + invoice_values = { + 'partner_id': record.buyer.id, # Buyer of the property + 'move_type': 'out_invoice', # Customer Invoice + 'invoice_line_ids': [ + (0, 0, { + 'name': ("Commission for selling the property"), + 'quantity': 1, + 'price_unit': commission_amount, + }), + (0, 0, { + 'name': ("Administrative Fees"), + 'quantity': 1, + 'price_unit': admin_fee, + }), + ], + } + + self.env['account.move'].create(invoice_values) + return super(estate_property, self).action_set_property_as_sold() \ No newline at end of file diff --git a/estate_account/security/ir.model.access.csv b/estate_account/security/ir.model.access.csv new file mode 100644 index 00000000000..3cb1bbd3c32 --- /dev/null +++ b/estate_account/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + +access_estate_account,access_estate_account,model_estate_account,base.group_user,1,1,1,1 \ No newline at end of file From 62cb1bcdd2c2129a5e750b627237e7ef76725187 Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Thu, 28 Nov 2024 18:47:13 +0530 Subject: [PATCH 04/16] [ADD] estate: Added demo data for estate model and some values are added Added new model for demo data and added XML file for that views Added 3 properties and added 3 offers in property for demonstration., --- estate/__manifest__.py | 5 ++ estate/demo/estate_property_demo.xml | 108 +++++++++++++++++++++++++++ estate/views/estate_menus.xml | 2 +- 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 estate/demo/estate_property_demo.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ff5c2f635ce..65a1debe523 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,6 +1,8 @@ { 'name': 'estate', 'version': '1.0', + 'author' : "DhruvKumar Nagar", + "description": "Real estate module for all your property needs!", 'depends': [ 'base', ], @@ -10,5 +12,8 @@ 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_menus.xml', + ], + 'demo':[ + 'demo/estate_property_demo.xml' ] } \ No newline at end of file diff --git a/estate/demo/estate_property_demo.xml b/estate/demo/estate_property_demo.xml new file mode 100644 index 00000000000..63b469a267a --- /dev/null +++ b/estate/demo/estate_property_demo.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <data noupdate="1"> + <record id="estate_property_type_residential" model="estate.property.type"> + <field name="name">Residential</field> + </record> + + <record id="property_big_villa" model="estate.property"> + <field name="name">Big Villa</field> + <field name="state">New</field> + <field name="description">A nice and big villa</field> + <field name="postcode">12345</field> + <field name="date_availability">2020-02-02</field> + <field name="expected_price">1600000</field> + <field name="selling_price">1450000</field> + <field name="bedrooms">6</field> + <field name="living_area">100</field> + <field name="facades">4</field> + <field name="garage">True</field> + <field name="garden">True</field> + <field name="garden_area">100000</field> + <field name="garden_orientation">south</field> + <field name="property_type_id" ref="estate_property_type_residential"></field> + </record> + + <record id="property_trailer_home" model="estate.property"> + <field name="name">Trailer Home</field> + <field name="state">Cancelled</field> + <field name="description">Home in a trailer park</field> + <field name="postcode">54321</field> + <field name="date_availability">1970-01-01</field> + <field name="expected_price">100000</field> + <field name="selling_price">0</field> + <field name="bedrooms">1</field> + <field name="living_area">10</field> + <field name="facades">4</field> + <field name="garage">False</field> + <field name="garden">False</field> + <field name="property_type_id" ref="estate_property_type_residential"></field> + </record> + + <record id="property_huanted_house" model="estate.property"> + <field name="name">Huanted Home</field> + <field name="state">Cancelled</field> + <field name="description">Home in a trailer park</field> + <field name="postcode">54321</field> + <field name="date_availability">1970-01-01</field> + <field name="expected_price">100000</field> + <field name="selling_price">0</field> + <field name="bedrooms">1</field> + <field name="living_area">10</field> + <field name="facades">4</field> + <field name="garage">False</field> + <field name="garden">False</field> + <field name="property_type_id" ref="estate_property_type_residential"></field> + <field name="offer_ids" + eval="[ + Command.create({ + 'price': 95000, + 'property_id': 'property_huanted_house', + 'partner_id': ref('base.res_partner_12'), + }), + Command.create({ + 'price': 98000, + 'property_id': 'property_huanted_house', + 'partner_id': ref('base.res_partner_12'), + }), + Command.create({ + 'price': 100000, + 'property_id': 'property_huanted_house', + 'partner_id': ref('base.res_partner_12'), + })]" /> + </record> + + <record id="offer_azure_villa_10000" model="estate.property.offer"> + <field name="partner_id" ref="base.res_partner_12" /> + <field name="property_id" ref="estate.property_big_villa" /> + <field name="price">10000</field> + <field name="validity">14</field> + <field name="date_deadline" + eval="datetime.now() + timedelta(days=14)" /> + + </record> + + <record id="offer_azure_villa_1500000" model="estate.property.offer"> + <field name="partner_id" ref="base.res_partner_12" /> + <field name="property_id" ref="estate.property_big_villa" /> + <field name="price">1500000</field> + <field name="validity">14</field> + <field name="date_deadline" + eval="datetime.now() + timedelta(days=14)" /> + + + </record> + + <record id="offer_deco_villa_1500001" model="estate.property.offer"> + <field name="partner_id" ref="base.res_partner_12" /> + <field name="property_id" ref="estate.property_big_villa" /> + <field name="price">1500001</field> + <field name="validity">14</field> + <field name="date_deadline" + eval="datetime.now() + timedelta(days=14)" /> + </record> + + + </data> + +</odoo> \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index bd194399cff..d277966a38c 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -15,4 +15,4 @@ action="property_tag_action" /> </menuitem> </menuitem> -</odoo> \ No newline at end of file +</odoo> \ No newline at end of file From 8f56fcb716627ac4730d0d60c6c186b97fe3c33e Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Fri, 29 Nov 2024 18:54:53 +0530 Subject: [PATCH 05/16] [ADD] estate: added new report pdfs I added code for property-related PDF files. All values are in the action section for easy download. I also added predefined demo data for easy to understand --- estate/__manifest__.py | 6 +++ .../estate_property_offers_templates.xml | 47 +++++++++++++++++++ estate/report/estate_property_reports.xml | 14 ++++++ estate/report/estate_property_templates.xml | 34 ++++++++++++++ estate/report/res_users_reports.xml | 12 +++++ estate/report/res_users_templates.xml | 20 ++++++++ 6 files changed, 133 insertions(+) create mode 100644 estate/report/estate_property_offers_templates.xml create mode 100644 estate/report/estate_property_reports.xml create mode 100644 estate/report/estate_property_templates.xml create mode 100644 estate/report/res_users_reports.xml create mode 100644 estate/report/res_users_templates.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 65a1debe523..38468d87c07 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,12 @@ 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_menus.xml', + 'report/estate_property_offers_templates.xml', + 'report/estate_property_templates.xml', + 'report/estate_property_reports.xml', + 'report/res_users_templates.xml', + 'report/res_users_reports.xml', + ], 'demo':[ 'demo/estate_property_demo.xml' diff --git a/estate/report/estate_property_offers_templates.xml b/estate/report/estate_property_offers_templates.xml new file mode 100644 index 00000000000..5982ee83cdf --- /dev/null +++ b/estate/report/estate_property_offers_templates.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <template id="offers_table"> + <table class="table"> + <thead> + <tr> + <th> + <strong>Price</strong> + </th> + <th> + <strong>Partner</strong> + </th> + <th> + <strong>Validity (days)</strong> + </th> + <th> + <strong>Deadline</strong> + </th> + <th> + <strong>State</strong> + </th> + </tr> + </thead> + <tbody> + <t t-foreach="doc.offer_ids" t-as="offer"> + <tr> + <td> + <span t-field="offer.price" /> + </td> + <td> + <span t-field="offer.partner_id.name" /> + </td> + <td> + <span t-field="offer.validity" /> + </td> + <td> + <span t-field="offer.date_deadline" /> + </td> + <td> + <span t-field="offer.status" /> + </td> + </tr> + </t> + </tbody> + </table> + </template> +</odoo> \ No newline at end of file diff --git a/estate/report/estate_property_reports.xml b/estate/report/estate_property_reports.xml new file mode 100644 index 00000000000..9e91edfe703 --- /dev/null +++ b/estate/report/estate_property_reports.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + + <record id="action_report_property" model="ir.actions.report"> + <field name="name">Property Offers</field> + <field name="model">estate.property</field> + <field name="report_type">qweb-pdf</field> + <field name="report_name">estate.report_property_template</field> + <field name="binding_model_id" ref="model_estate_property" /> + <field name="binding_type">report</field> + </record> + +</odoo> \ No newline at end of file diff --git a/estate/report/estate_property_templates.xml b/estate/report/estate_property_templates.xml new file mode 100644 index 00000000000..f3745876da6 --- /dev/null +++ b/estate/report/estate_property_templates.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <template id="report_property_template"> + <t t-call="web.html_container"> + <t t-foreach="docs" t-as="doc"> + <t t-call="web.external_layout"> + <div class="page"> + <h2 style="margin-bottom: 10px;">Property: <span t-field="doc.name" /></h2> + <p> + <strong>Salesman:</strong> + <span t-field="doc.salesman" /> + <br /> + <strong>Expected + Price:</strong> + <span t-field="doc.expected_price" /> + <br /> + <strong> + Status:</strong> + <span t-field="doc.state" /> + </p> + <t t-if="doc.offer_ids"> + <t t-call="estate.offers_table"> </t> + </t> + <t t-else=""> + <p>No offers yet for this property.</p> + </t> + </div> + </t> + </t> + </t> + </template> + +</odoo> \ No newline at end of file diff --git a/estate/report/res_users_reports.xml b/estate/report/res_users_reports.xml new file mode 100644 index 00000000000..9cefed96453 --- /dev/null +++ b/estate/report/res_users_reports.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="action_report_property_users" model="ir.actions.report"> + <field name="name">users report</field> + <field name="model">res.users</field> + <field name="report_type">qweb-pdf</field> + <field name="report_name">estate.report_user_properties</field> + <field name="binding_model_id" ref="base.model_res_users" /> + <field name="binding_type">report</field> + </record> +</odoo> \ No newline at end of file diff --git a/estate/report/res_users_templates.xml b/estate/report/res_users_templates.xml new file mode 100644 index 00000000000..095f183f97b --- /dev/null +++ b/estate/report/res_users_templates.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <template id="report_user_properties"> + <t t-call="web.external_layout"> + <div class="page"> + <h2>Properties for: <span t-field="doc.name" /></h2> + <t t-foreach="doc.property_ids" t-as="property"> + <div> + <h3> + <t t-field="property.name" /> + </h3> + <p>Expected Price: <t t-field="property.expected_price" /> €</p> + <p>Status: <t t-field="property.state" /></p> + <t t-call="estate.offers_table" /> + </div> + </t> + </div> + </t> + </template> +</odoo> \ No newline at end of file From 4694f9cba3f48d9db5bef7ed5069ed52e70ca30e Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Mon, 2 Dec 2024 18:55:39 +0530 Subject: [PATCH 06/16] [ADD] estate: Added code for security rules and pdf reports Changed offer code in sub-template to better reusability of code and also Added new report for use-wise property allocated to them and inherited account module to make sure the invoice is created or not in the report. added security rules and defined groups related to all models and made sure Users have the right access to do CRUD on that. I also added record rules to define security on specific records and learned about how to bypass the security --- estate/__manifest__.py | 4 +- estate/models/__init__.py | 3 +- estate/models/estate_property.py | 3 +- estate/models/res_users.py | 3 +- .../estate_property_offers_templates.xml | 58 +++++++--------- estate/report/estate_property_reports.xml | 5 +- estate/report/estate_property_templates.xml | 64 +++++++++++------ estate/report/res_users_reports.xml | 5 +- estate/report/res_users_templates.xml | 66 ++++++++++++++---- estate/security/ir.model.access.csv | 10 +-- estate/security/record_rules.xml | 18 +++++ estate/security/security.xml | 20 ++++++ estate/views/estate_property_views.xml | 2 +- estate_account/__manifest__.py | 2 +- estate_account/models/__init__.py | 2 +- estate_account/models/account_move.py | 8 +++ estate_account/models/estate_property.py | 68 ++++++++++--------- .../estate_property_report_inherited.xml | 10 +++ 18 files changed, 231 insertions(+), 120 deletions(-) create mode 100644 estate/security/record_rules.xml create mode 100644 estate/security/security.xml create mode 100644 estate_account/models/account_move.py create mode 100644 estate_account/report/estate_property_report_inherited.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 38468d87c07..93414c11325 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,8 +8,11 @@ ], 'installable': True, 'application': True, + 'category': "Real Estate/Brokerage", 'data': [ 'security/ir.model.access.csv', + 'security/security.xml', + 'security/record_rules.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml', 'report/estate_property_offers_templates.xml', @@ -17,7 +20,6 @@ 'report/estate_property_reports.xml', 'report/res_users_templates.xml', 'report/res_users_reports.xml', - ], 'demo':[ 'demo/estate_property_demo.xml' diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 09b2099fe84..a9459ed5906 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,5 @@ from . import estate_property from . import estate_property_type from . import estate_property_tag -from . import estate_property_offer \ No newline at end of file +from . import estate_property_offer +from . import res_users \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e35a7eaa8b9..25fdb257740 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -27,7 +27,7 @@ class estate_property(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection( selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]) - salesman = fields.Many2one("res.users", string='Salesperson', default=lambda self: self.env.user) + salesman_id = fields.Many2one("res.users", string='Salesperson', default=lambda self: self.env.user) buyer = fields.Many2one("res.partner", copy=False) property_type_id = fields.Many2one("estate.property.type") tag_ids = fields.Many2many("estate.property.tag") @@ -36,6 +36,7 @@ class estate_property(models.Model): best_offer = fields.Float(compute="_compute_best_offer") sequence = fields.Integer(string="Sequence", default=1) + company_id = fields.Integer(default=lambda self: self.env.user.company_id) _sql_constraints = [ ('check_expected_price', 'CHECK(expected_price >= 0 AND selling_price >= 0)', 'The expected price and selling price should be greater than equal to zero.') diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 781c1a73a24..8c5e63fb8c9 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -4,8 +4,7 @@ class users_inherited(models.Model): _inherit = "res.users" property_ids = fields.One2many( - comodel_name="estate.property", - inverse_name="salesman", + "estate.property", "salesman_id", string="Properties", domain="[('state', 'in', ['New', 'Offer Received'])]" ) \ No newline at end of file diff --git a/estate/report/estate_property_offers_templates.xml b/estate/report/estate_property_offers_templates.xml index 5982ee83cdf..8b886f67a33 100644 --- a/estate/report/estate_property_offers_templates.xml +++ b/estate/report/estate_property_offers_templates.xml @@ -1,47 +1,37 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> + <template id="offers_table"> <table class="table"> <thead> <tr> - <th> - <strong>Price</strong> - </th> - <th> - <strong>Partner</strong> - </th> - <th> - <strong>Validity (days)</strong> - </th> - <th> - <strong>Deadline</strong> - </th> - <th> - <strong>State</strong> - </th> + <th>Price</th> + <th>Partner</th> + <th>Validity</th> + <th>Deadline</th> + <th>Status</th> </tr> </thead> <tbody> - <t t-foreach="doc.offer_ids" t-as="offer"> - <tr> - <td> - <span t-field="offer.price" /> - </td> - <td> - <span t-field="offer.partner_id.name" /> - </td> - <td> - <span t-field="offer.validity" /> - </td> - <td> - <span t-field="offer.date_deadline" /> - </td> - <td> - <span t-field="offer.status" /> - </td> - </tr> - </t> + <tr t-foreach="offers" t-as="offer"> + <td> + <span t-field="offer.price" /> + </td> + <td> + <span t-field="offer.partner_id" /> + </td> + <td> + <span t-field="offer.validity" /> + </td> + <td> + <span t-field="offer.date_deadline" /> + </td> + <td> + <span t-field="offer.status" /> + </td> + </tr> </tbody> </table> </template> + </odoo> \ No newline at end of file diff --git a/estate/report/estate_property_reports.xml b/estate/report/estate_property_reports.xml index 9e91edfe703..6a41ad893e1 100644 --- a/estate/report/estate_property_reports.xml +++ b/estate/report/estate_property_reports.xml @@ -1,14 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> - - <record id="action_report_property" model="ir.actions.report"> <field name="name">Property Offers</field> <field name="model">estate.property</field> <field name="report_type">qweb-pdf</field> - <field name="report_name">estate.report_property_template</field> + <field name="report_name">estate.estate_property_report_template</field> <field name="binding_model_id" ref="model_estate_property" /> <field name="binding_type">report</field> </record> - </odoo> \ No newline at end of file diff --git a/estate/report/estate_property_templates.xml b/estate/report/estate_property_templates.xml index f3745876da6..6d820d995a3 100644 --- a/estate/report/estate_property_templates.xml +++ b/estate/report/estate_property_templates.xml @@ -1,34 +1,58 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="UTF-8"?> <odoo> - - <template id="report_property_template"> - <t t-call="web.html_container"> - <t t-foreach="docs" t-as="doc"> + <template id="estate_property_report_template"> + <t t-foreach="docs" t-as="property"> + <t t-call="web.html_container"> <t t-call="web.external_layout"> <div class="page"> - <h2 style="margin-bottom: 10px;">Property: <span t-field="doc.name" /></h2> - <p> - <strong>Salesman:</strong> - <span t-field="doc.salesman" /> + <div> + <t t-set="company" t-value="property.salesman_id.company_id" /> + <div> + <img t-att-src="image_data_uri(company.logo)" alt="Company Logo" + style="width: 200px; height: auto;" /> + </div> + <hr /> + <span t-field="company.name" /> + <br /> + <span t-field="company.street" /> + <span t-field="company.street2" /> + <br /> + <span t-field="company.city" t-if="company.city" /> + <span t-field="company.state_id.code" t-if="company.state_id.code" /> + <span t-field="company.zip" t-if="company.zip" /> + <br /> + <span t-field="company.country_id" /> <br /> - <strong>Expected - Price:</strong> - <span t-field="doc.expected_price" /> + </div> + <br /> + <h2> + <span t-field="property.name" /> + </h2> + <div id="target"> + <strong>Salesman: </strong> + <span t-field="property.salesman_id" /> <br /> - <strong> - Status:</strong> - <span t-field="doc.state" /> - </p> - <t t-if="doc.offer_ids"> - <t t-call="estate.offers_table"> </t> + <strong>Expected Price: </strong> + <span t-field="property.expected_price" /> + <br /> + <strong>State: </strong> + <span t-field="property.state" /> + </div> + <t t-set="offers" t-value="property.mapped('offer_ids')" /> + <!-- Call the sub-template for offers --> + <t t-if="offers"> + <t t-call="estate.offers_table" /> </t> + <t t-else=""> - <p>No offers yet for this property.</p> + <p style="margin: 20px 0; font-weight: bold;"> + No offers yet for this property :( + </p> </t> </div> </t> </t> </t> - </template> + </template> </odoo> \ No newline at end of file diff --git a/estate/report/res_users_reports.xml b/estate/report/res_users_reports.xml index 9cefed96453..79b5abd0bb8 100644 --- a/estate/report/res_users_reports.xml +++ b/estate/report/res_users_reports.xml @@ -1,11 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> - <record id="action_report_property_users" model="ir.actions.report"> - <field name="name">users report</field> + <field name="name">Users Report</field> <field name="model">res.users</field> <field name="report_type">qweb-pdf</field> - <field name="report_name">estate.report_user_properties</field> + <field name="report_name">estate.res_users_report_template</field> <field name="binding_model_id" ref="base.model_res_users" /> <field name="binding_type">report</field> </record> diff --git a/estate/report/res_users_templates.xml b/estate/report/res_users_templates.xml index 095f183f97b..96b39005633 100644 --- a/estate/report/res_users_templates.xml +++ b/estate/report/res_users_templates.xml @@ -1,20 +1,58 @@ -<?xml version="1.0" encoding="utf-8"?> <odoo> - <template id="report_user_properties"> - <t t-call="web.external_layout"> - <div class="page"> - <h2>Properties for: <span t-field="doc.name" /></h2> - <t t-foreach="doc.property_ids" t-as="property"> - <div> - <h3> - <t t-field="property.name" /> - </h3> - <p>Expected Price: <t t-field="property.expected_price" /> €</p> - <p>Status: <t t-field="property.state" /></p> - <t t-call="estate.offers_table" /> + <template id="res_users_report_template"> + <t t-call="web.html_container"> + <t t-call="web.external_layout"> + <t t-foreach="docs" t-as="user"> + <div class="page"> + <div class="mb32"> + <t t-set="company" t-value="user.company_id" /> + <div> + <img t-att-src="image_data_uri(company.logo)" + alt="Company Logo" + style="width: 200px; height: auto;" /> + </div> + <hr /> + <span t-field="company.name" /> + <br /> + <span t-field="company.street" /> + <span t-field="company.street2" /> + <br /> + <span t-field="company.city" t-if="company.city" /> + <span t-field="company.state_id.code" + t-if="company.state_id.code" /> + <span t-field="company.zip" t-if="company.zip" /> + <br /> + <span t-field="company.country_id" /> + </div> + <h1>Salesman: <span t-field="user.name" /></h1> + <t t-if="user.property_ids"> + <t t-foreach="user.property_ids" t-as="property"> + <h2> + <span t-field="property.name" /> + </h2> + <div> + <strong>Expected Price: </strong> + <span t-field="property.expected_price" /> + <br /> + <strong>Status: </strong> + <span t-field="property.state" /> + </div> + <t t-set="offers" t-value="property.mapped('offer_ids')" /> + <t t-if="offers"> + <t t-call="estate.offers_table" /> + </t> + <t t-else=""> + <p + style="margin: 20px 0; font-weight: bold;"> + No offers yet for this property :( + </p> + </t> + + </t> + </t> </div> </t> - </div> + </t> </t> </template> </odoo> \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index f04c59826ce..0c56e67cc52 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,6 +1,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 -access_estate__property_type,access_estate__property_type,model_estate_property_type,base.group_user,1,1,1,1 -access_estate__property_tag,access_estate__property_tag,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate__property_offer,access_estate__property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_manager,res.real.estate.manager,model_estate_property,estate_group_manager,1,1,1,1 +access_estate_offer_user,res.real.estate.manager,model_estate_property_offer,estate_group_manager,1,1,1,1 +access_estate_type_user,res.real.estate.type.user,model_estate_property_type,estate_group_user,1,0,0,0 +access_estate_tag_user,res.real.estate.tag.user,model_estate_property_tag,estate_group_user,1,0,0,0 +access_estate_property_user,res.real.estate.property.user,model_estate_property,estate_group_user,1,1,1,0 +access_estate_offer_user,res.real.estate.manager,model_estate_property_offer,estate_group_user,1,0,0,0 diff --git a/estate/security/record_rules.xml b/estate/security/record_rules.xml new file mode 100644 index 00000000000..8a109b099e7 --- /dev/null +++ b/estate/security/record_rules.xml @@ -0,0 +1,18 @@ +<odoo> + <!-- Rule for Real Estate Agents --> + <record id="estate_agent_rule" model="ir.rule"> + <field name="name">Agent: Access their own properties</field> + <field name="model_id" ref="model_estate_property" /> + <field name="domain_force">['|', ('salesman_id', '=', False), ('salesman_id', '=', + user.id)]</field> + <field name="groups" eval="[(4, ref('estate_group_user'))]" /> + </record> + + <!-- Rule for Real Estate Managers --> + <record id="estate_manager_rule" model="ir.rule"> + <field name="name">Manager: Access all properties</field> + <field name="model_id" ref="model_estate_property" /> + <field name="domain_force">[]</field> + <field name="groups" eval="[(4, ref('estate_group_manager'))]" /> + </record> +</odoo> \ No newline at end of file diff --git a/estate/security/security.xml b/estate/security/security.xml new file mode 100644 index 00000000000..c4801b5742a --- /dev/null +++ b/estate/security/security.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="base.module_category_real_estate_brokerage" model="ir.module.category"> + <field name="description">Helps you manage your real estate assets.</field> + <field name="sequence">9</field> + </record> + + <record id="estate_group_user" model="res.groups"> + <field name="name">Agent</field> + <field name="category_id" ref="base.module_category_real_estate_brokerage" /> + </record> + + <record id="estate_group_manager" model="res.groups"> + <field name="name">Manager</field> + <field name="category_id" ref="base.module_category_real_estate_brokerage" /> + <field name="implied_ids" eval="[(4, ref('estate_group_user'))]" /> + </record> + +</odoo> \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 0c667f840eb..8761a224978 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -182,7 +182,7 @@ <page string="Other Info"> <group> - <field name="salesman" class="mb16" /> + <field name="salesman_id" class="mb16" /> <field name="buyer" class="mb16" /> </group> </page> diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index 93f77f71ade..8f803d55c73 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -3,7 +3,6 @@ 'version': '1.0', 'category': 'Accounting', 'depends': ['base', 'estate', 'account'], - 'data': [], 'installable': True, 'application': False, 'auto_install': False, @@ -13,4 +12,5 @@ ===================== This module integrates the Estate module with the Account module. """, + 'data': ['report/estate_property_report_inherited.xml'] } diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py index f4c8fd6db6d..bd195fa00c1 100644 --- a/estate_account/models/__init__.py +++ b/estate_account/models/__init__.py @@ -1 +1 @@ -from . import estate_property \ No newline at end of file +from . import estate_property,account_move \ No newline at end of file diff --git a/estate_account/models/account_move.py b/estate_account/models/account_move.py new file mode 100644 index 00000000000..199bca07190 --- /dev/null +++ b/estate_account/models/account_move.py @@ -0,0 +1,8 @@ +from odoo import fields, models + +class InheritedAccountMove(models.Model): + _inherit="account.move" + + property_id = fields.Many2one('estate.property',string="Property") + estate_property_id = fields.Many2one('estate_account.property') + \ No newline at end of file diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 828e4cc8ac3..14fc3c98632 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -1,36 +1,38 @@ - -from odoo import models - -class estate_property(models.Model): - _inherit = 'estate.property' - +from odoo import Command,models,fields +from odoo.exceptions import AccessError +class EstateProperty(models.Model): + _description = "Estate Account property Model" + _inherit="estate.property" + invoice_line_ids = fields.One2many('account.move','property_id') + quantity=fields.Integer(string="Quantity") + def action_set_property_as_sold(self): - for record in self: - if not record.buyer: - raise ValueError(("Please set a buyer before selling the property.")) - - if not record.selling_price: - raise ValueError(("Please set a selling price before selling the property.")) - - commission_amount = record.selling_price * 0.06 - admin_fee = 100.00 + #checking access rights + try: + self.check_access('write') + except AccessError as e: + raise AccessError("You do not have permission to update this property.") - invoice_values = { - 'partner_id': record.buyer.id, # Buyer of the property - 'move_type': 'out_invoice', # Customer Invoice - 'invoice_line_ids': [ - (0, 0, { - 'name': ("Commission for selling the property"), - 'quantity': 1, - 'price_unit': commission_amount, - }), - (0, 0, { - 'name': ("Administrative Fees"), - 'quantity': 1, - 'price_unit': admin_fee, - }), - ], - } - self.env['account.move'].create(invoice_values) - return super(estate_property, self).action_set_property_as_sold() \ No newline at end of file + move_values = { + 'partner_id': self.buyer.id, + 'move_type': 'out_invoice', + 'property_id':self.id, + 'invoice_line_ids': [ + Command.create({ + "name":self.name, + "quantity": 1, + "price_unit": 0.6 * self.selling_price, # 6% of the S.P + }), + + Command.create({ + "name": "Administrative fees", + "quantity": 1, + "price_unit": 100.00, + }), + ] + } + print(" reached ".center(100, '=')) + self.env['account.move'].sudo().create(move_values) + + return super().action_set_property_as_sold() \ No newline at end of file diff --git a/estate_account/report/estate_property_report_inherited.xml b/estate_account/report/estate_property_report_inherited.xml new file mode 100644 index 00000000000..182e051b9e1 --- /dev/null +++ b/estate_account/report/estate_property_report_inherited.xml @@ -0,0 +1,10 @@ +<odoo> + <template id="estate_property_report_inherited" + inherit_id="estate.estate_property_report_template"> + <xpath expr="//div[contains(@id, 'target')]" position="after"> + <h5> + <strong>!!! Invoice has already been created !!!</strong> + </h5> + </xpath> + </template> +</odoo> \ No newline at end of file From 04e9366ea3099d4e1a79af65bb8c8a24dd926c8b Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Tue, 3 Dec 2024 14:37:27 +0530 Subject: [PATCH 07/16] [ADD] estate: Added code offer wizard for creating offers on multiple properties created a wizard module and template we can select all the properties we have to create the offer and click on the action button makes an offer to create an offer on all the properties. --- estate/__init__.py | 3 +- estate/__manifest__.py | 3 +- estate/security/ir.model.access.csv | 3 +- estate/views/estate_property_views.xml | 42 +++++++++++++++---- estate/wizard/__init__.py | 1 + estate/wizard/estate_property_offer_wizard.py | 21 ++++++++++ .../estate_property_offer_wizard_view.xml | 24 +++++++++++ 7 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 estate/wizard/__init__.py create mode 100644 estate/wizard/estate_property_offer_wizard.py create mode 100644 estate/wizard/estate_property_offer_wizard_view.xml diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..9b4296142f4 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ -from . import models \ No newline at end of file +from . import models +from . import wizard diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 93414c11325..eb5e4146e32 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,7 +4,7 @@ 'author' : "DhruvKumar Nagar", "description": "Real estate module for all your property needs!", 'depends': [ - 'base', + 'base','sale' ], 'installable': True, 'application': True, @@ -15,6 +15,7 @@ 'security/record_rules.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml', + 'wizard/estate_property_offer_wizard_view.xml', 'report/estate_property_offers_templates.xml', 'report/estate_property_templates.xml', 'report/estate_property_reports.xml', diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0c56e67cc52..413de74e96c 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -5,4 +5,5 @@ access_estate_offer_user,res.real.estate.manager,model_estate_property_offer,est access_estate_type_user,res.real.estate.type.user,model_estate_property_type,estate_group_user,1,0,0,0 access_estate_tag_user,res.real.estate.tag.user,model_estate_property_tag,estate_group_user,1,0,0,0 access_estate_property_user,res.real.estate.property.user,model_estate_property,estate_group_user,1,1,1,0 -access_estate_offer_user,res.real.estate.manager,model_estate_property_offer,estate_group_user,1,0,0,0 +access_estate_offer_user,res.real.estate.manager,model_estate_property_offer,estate_group_user,1,1,1,1 +access_estate_offer_wizard_user,res.real.estate.offer.wizard.manager,model_estate_property_offer_wizard,estate_group_manager,1,1,1,1 diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 8761a224978..8061ffb6ff2 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -7,19 +7,22 @@ <field name="view_mode">list,form,kanban</field> </record> - <record id="property_type_action" model="ir.actions.act_window"> + <record + id="property_type_action" model="ir.actions.act_window"> <field name="name">estate.property.type.action</field> <field name="res_model">estate.property.type</field> <field name="view_mode">list,form</field> </record> - <record id="property_tag_action" model="ir.actions.act_window"> + <record id="property_tag_action" + model="ir.actions.act_window"> <field name="name">estate.property.tag.action</field> <field name="res_model">estate.property.tag</field> <field name="view_mode">list,form</field> </record> - <record id="estate_property_offer_action" model="ir.actions.act_window"> + <record + id="estate_property_offer_action" model="ir.actions.act_window"> <field name="name">Offers</field> <field name="res_model">estate.property.offer</field> <field name="view_mode">list,form</field> @@ -27,7 +30,8 @@ </record> - <record id="estate_property_offer_tree_view" model="ir.ui.view"> + <record + id="estate_property_offer_tree_view" model="ir.ui.view"> <field name="name">estate.property.offer.tree</field> <field name="model">estate.property.offer</field> <field name="arch" type="xml"> @@ -39,7 +43,8 @@ </field> </record> - <record id="estate_property_kanban_view" model="ir.ui.view"> + <record + id="estate_property_kanban_view" model="ir.ui.view"> <field name="name">estate.property.kanban</field> <field name="model">estate.property</field> <field name="arch" type="xml"> @@ -90,7 +95,8 @@ </record> - <record id="estate_property_tree_view" model="ir.ui.view"> + <record + id="estate_property_tree_view" model="ir.ui.view"> <field name="name">estate.property.tree</field> <field name="model">estate.property</field> <field name="arch" type="xml"> @@ -107,8 +113,24 @@ </field> </record> + <record id="make_offer_to_properties" + model="ir.actions.server"> + <field name="name">Make Offer</field> + <field name="model_id" ref="estate.model_estate_property" /> + <field name="binding_model_id" ref="estate.model_estate_property" /> + <field name="state">code</field> + <field name="code"> + action = { + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "estate.property.offer.wizard", + "target":"new", + "context": {"records" : records.ids} + }</field> + </record> - <record id="estate_property_view_form" model="ir.ui.view"> + <record + id="estate_property_view_form" model="ir.ui.view"> <field name="name">estate.property.form</field> <field name="model">estate.property</field> <field name="arch" type="xml"> @@ -192,7 +214,8 @@ </field> </record> - <record id="estate_property_type_view_form" model="ir.ui.view"> + <record + id="estate_property_type_view_form" model="ir.ui.view"> <field name="name">estate.property.type.form</field> <field name="model">estate.property.type</field> <field name="arch" type="xml"> @@ -247,7 +270,8 @@ </record> --> - <record id="estate_property_search" model="ir.ui.view"> + <record + id="estate_property_search" model="ir.ui.view"> <field name="name">estate.property.search</field> <field name="model">estate.property</field> <field name="arch" type="xml"> diff --git a/estate/wizard/__init__.py b/estate/wizard/__init__.py new file mode 100644 index 00000000000..78122fb9f31 --- /dev/null +++ b/estate/wizard/__init__.py @@ -0,0 +1 @@ +from . import estate_property_offer_wizard \ No newline at end of file diff --git a/estate/wizard/estate_property_offer_wizard.py b/estate/wizard/estate_property_offer_wizard.py new file mode 100644 index 00000000000..3299a07cf07 --- /dev/null +++ b/estate/wizard/estate_property_offer_wizard.py @@ -0,0 +1,21 @@ +from odoo import models, fields + +class estate_property_offer_wizard(models.TransientModel): + _name = "estate.property.offer.wizard" + _description = "Make an offer wizard for multiple properties" + + price = fields.Float() + validity = fields.Integer(default=7) + buyer = fields.Many2one("res.partner", copy=False) + + def make_an_offer(self): + properties = self.env['estate.property'].browse(self._context.get("records", [])) + for property in properties: + self.env['estate.property.offer'].create({ + 'price': self.price, + 'property_id': property.id, + 'validity': self.validity, + 'partner_id': self.buyer.id, + }) + + return {'type': 'ir.actions.act_window_close'} \ No newline at end of file diff --git a/estate/wizard/estate_property_offer_wizard_view.xml b/estate/wizard/estate_property_offer_wizard_view.xml new file mode 100644 index 00000000000..4c1b2f79df1 --- /dev/null +++ b/estate/wizard/estate_property_offer_wizard_view.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="offer_wizard_form" model="ir.ui.view"> + <field name="name">estate.property.offer.wizard.form</field> + <field name="model">estate.property.offer.wizard</field> + <field name="arch" type="xml"> + <form string="Offer Wizard"> + <sheet> + <group> + <field name="price"></field> + <field name="validity"></field> + <field name="buyer"></field> + </group> + </sheet> + <footer> + <button name="make_an_offer" string="Make an offer" type="object" /> + <button string="Cancel" special="cancel" /> + </footer> + </form> + </field> + </record> + +</odoo> \ No newline at end of file From 53d9679f8eb4f9af1cfec0399a3f1e3d9bc67adc Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Tue, 3 Dec 2024 17:36:11 +0530 Subject: [PATCH 08/16] [FIX] estate: Added real estate properties view in res user model I added a real estate properties view in the user view for easy access to how many properties are assigned to this user. I have inherited the base view form and changed the view. --- estate/models/estate_property.py | 1 - estate/views/estate_property_views.xml | 21 +++++++-------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 25fdb257740..927d3aef27b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -94,7 +94,6 @@ def _check_selling_price(self): if float_is_zero(record.selling_price, precision_rounding=2): continue min_price = 0.9 * record.expected_price - print("min price : ", min_price) if float_compare(record.selling_price, min_price, precision_rounding=2) < 0: raise ValidationError( ("The selling price (%.2f) cannot be lower than 90%% of the expected price (%.2f).") diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 8061ffb6ff2..5abaa813565 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -249,25 +249,18 @@ </field> </record> - <!-- <record id="view_users_form_inherit_properties" model="ir.ui.view"> - <field name="name">res.users.form.inherit.properties</field> + <record id="res_users_view_form" model="ir.ui.view"> + <field name="name">res.users.view.form.inherit.gamification</field> <field name="model">res.users</field> <field name="inherit_id" ref="base.view_users_form" /> <field name="arch" type="xml"> - <xpath expr="//notebook" position="inside"> - <page string="Properties"> - <field name="property_ids" - context="{'form_view_ref': 'estate.estate_property_view_form'}"> - <tree> - <field name="name" /> - <field name="state" /> - <field name="expected_price" /> - </tree> - </field> + <notebook> + <page name="userSpecificProperties" string="Real Estate Properties"> + <field name="property_ids"></field> </page> - </xpath> + </notebook> </field> - </record> --> + </record> <record From dda0eb11755ffa58e616bd5d48e63d34a805b7f5 Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Wed, 4 Dec 2024 18:50:35 +0530 Subject: [PATCH 09/16] [ADD] estate: Added code for warranty confugration model added code for the warranty model and inherited the model like the sale.order, product, and added views for that. added code for Wizard warranty and defined its view. --- warranty/__init__.py | 2 ++ warranty/__manifest__.py | 17 +++++++++++ warranty/models/__init__.py | 1 + warranty/models/warranty.py | 23 ++++++++++++++ warranty/security/ir.model.access.csv | 4 +++ warranty/views/warranty_menu.xml | 20 ++++++++++++ warranty/views/warranty_views.xml | 39 ++++++++++++++++++++++++ warranty/wizard/__init__.py | 1 + warranty/wizard/warranty_wizard.py | 14 +++++++++ warranty/wizard/warranty_wizard_view.xml | 20 ++++++++++++ 10 files changed, 141 insertions(+) create mode 100644 warranty/__init__.py create mode 100644 warranty/__manifest__.py create mode 100644 warranty/models/__init__.py create mode 100644 warranty/models/warranty.py create mode 100644 warranty/security/ir.model.access.csv create mode 100644 warranty/views/warranty_menu.xml create mode 100644 warranty/views/warranty_views.xml create mode 100644 warranty/wizard/__init__.py create mode 100644 warranty/wizard/warranty_wizard.py create mode 100644 warranty/wizard/warranty_wizard_view.xml diff --git a/warranty/__init__.py b/warranty/__init__.py new file mode 100644 index 00000000000..c536983e2b2 --- /dev/null +++ b/warranty/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard \ No newline at end of file diff --git a/warranty/__manifest__.py b/warranty/__manifest__.py new file mode 100644 index 00000000000..9357834e601 --- /dev/null +++ b/warranty/__manifest__.py @@ -0,0 +1,17 @@ +{ + 'name': 'Warranty', + 'version': '1.0', + 'category': 'Sales', + 'depends': ['base', 'sale'], + 'installable': True, + 'application': False, + 'auto_install': False, + 'summary': 'Warranty related to products.', + + 'data': [ + 'security/ir.model.access.csv', + 'views/warranty_views.xml', + 'views/warranty_menu.xml', + 'wizard/warranty_wizard_view.xml' + ] +} diff --git a/warranty/models/__init__.py b/warranty/models/__init__.py new file mode 100644 index 00000000000..c8dacb00353 --- /dev/null +++ b/warranty/models/__init__.py @@ -0,0 +1 @@ +from . import warranty \ No newline at end of file diff --git a/warranty/models/warranty.py b/warranty/models/warranty.py new file mode 100644 index 00000000000..3b01fff9430 --- /dev/null +++ b/warranty/models/warranty.py @@ -0,0 +1,23 @@ +from odoo import fields, models + + +class warranty(models.Model): + _name = "warranty" + _description = "warranty model for products" + + name = fields.Char(required=True) + product_ids = fields.Many2one(comodel_name="product.product") + percentage = fields.Float(DecimalPrecision=2) + + +class ProductProduct(models.Model): + _inherit = "product.product" + + is_warranty_available = fields.Boolean() + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + def add_warranty_to_product(self): + pass \ No newline at end of file diff --git a/warranty/security/ir.model.access.csv b/warranty/security/ir.model.access.csv new file mode 100644 index 00000000000..5b8cfb70349 --- /dev/null +++ b/warranty/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + +access_warranty_manager,res.warranty.managar,model_warranty,base.group_user,1,1,1,1 +access_warranty_wizard_manager,res.warranty.wizard.managar,model_warranty_product_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/warranty/views/warranty_menu.xml b/warranty/views/warranty_menu.xml new file mode 100644 index 00000000000..7682265cfec --- /dev/null +++ b/warranty/views/warranty_menu.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <!-- Define the Action --> + <record id="warranty_configuration_action" model="ir.actions.act_window"> + <field name="name">Warranty Configuration</field> + <field name="res_model">warranty</field> + <field name="view_mode">list</field> + <field name="help" type="html"> + <p>This action allows you to configure warranty settings.</p> + </field> + </record> + + <!-- Define the Menu Item --> + <menuitem + id="warranty_menu" + name="Warranty Configuration" + action="warranty_configuration_action" + parent="sale.menu_sale_config" + sequence="2" /> +</odoo> \ No newline at end of file diff --git a/warranty/views/warranty_views.xml b/warranty/views/warranty_views.xml new file mode 100644 index 00000000000..f1451c9fbe6 --- /dev/null +++ b/warranty/views/warranty_views.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <!-- Warranty List View --> + <record id="warranty_list_view" model="ir.ui.view"> + <field name="name">warranty.list</field> + <field name="model">warranty</field> + <field name="arch" type="xml"> + <list string="warranty list"> + <field name="name" string="Name" /> + <field name="product_ids" string="Product" /> + <field name="percentage" string="Percentage" /> + </list> + </field> + </record> + + <record id="is_warranty_available_button" model="ir.ui.view"> + <field name="name">product.template.is.warranty.available</field> + <field name="model">product.product</field> + <field name="inherit_id" ref="product.product_normal_form_view" /> + <field name="arch" type="xml"> + <field name="product_tag_ids" position="after"> + <field name="is_warranty_available" + string="Is Warranty Available" /> + </field> + </field> + </record> + + <record id="add_warranty_button_cust" model="ir.ui.view"> + <field name="name">sale.order.add.warranty.button.cust</field> + <field name="model">sale.order</field> + <field name="inherit_id" ref="sale.view_order_form" /> + <field name="arch" type="xml"> + <group name="note_group" position="before"> + <button name="add_warranty_to_product" type="object" class="btn btn-secondary" + string="Add Warranty" /> + </group> + </field> + </record> +</odoo> \ No newline at end of file diff --git a/warranty/wizard/__init__.py b/warranty/wizard/__init__.py new file mode 100644 index 00000000000..4ee22aff5b8 --- /dev/null +++ b/warranty/wizard/__init__.py @@ -0,0 +1 @@ +from . import warranty_wizard \ No newline at end of file diff --git a/warranty/wizard/warranty_wizard.py b/warranty/wizard/warranty_wizard.py new file mode 100644 index 00000000000..81b939d4c1f --- /dev/null +++ b/warranty/wizard/warranty_wizard.py @@ -0,0 +1,14 @@ +from odoo import models, fields, api + +class WarrantyProductWizard(models.TransientModel): + _name = 'warranty.product.wizard' + _description = 'Warranty Product Wizard' + + product_id = fields.One2many("") + year = fields.Many2one(comodel_name="warranty") + end_date = fields.Date(readonly=True, compute="_compute_end_date"); + + + @api.depends("year") + def _compute_end_date(self): + pass diff --git a/warranty/wizard/warranty_wizard_view.xml b/warranty/wizard/warranty_wizard_view.xml new file mode 100644 index 00000000000..2be8a55795f --- /dev/null +++ b/warranty/wizard/warranty_wizard_view.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> + +<odoo> + <record id="view_warranty_product_wizard" model="ir.ui.view"> + <field name="name">warranty.product.wizard.form</field> + <field name="model">warranty.product.wizard</field> + <field name="arch" type="xml"> + <form string="Warranty Products"> + <group> + <field name="product_ids" widget="many2many_tags" /> + </group> + <footer> + <button string="Add" type="object" name="action_display_products" + class="btn-primary" /> + <button string="Cancel" class="btn-default" special="cancel" /> + </footer> + </form> + </field> + </record> +</odoo> \ No newline at end of file From e128fb7141dd69fbf992ed848e9bb38c30dd461e Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Thu, 5 Dec 2024 18:43:05 +0530 Subject: [PATCH 10/16] [ADD] warranty: refactor of the code and added sale order lines Added code for wizard view and added code for warranty lines to and done code for all reference models and connected that model to sale.line model. --- warranty/__manifest__.py | 2 +- warranty/models/__init__.py | 2 +- warranty/models/warranty.py | 23 ------- warranty/models/warranty_configuration.py | 22 ++++++ warranty/security/ir.model.access.csv | 5 +- warranty/views/warranty_menu.xml | 2 +- warranty/views/warranty_views.xml | 7 +- warranty/wizard/warranty_wizard.py | 81 ++++++++++++++++++++--- warranty/wizard/warranty_wizard_view.xml | 35 +++++++--- 9 files changed, 129 insertions(+), 50 deletions(-) delete mode 100644 warranty/models/warranty.py create mode 100644 warranty/models/warranty_configuration.py diff --git a/warranty/__manifest__.py b/warranty/__manifest__.py index 9357834e601..95a9994652a 100644 --- a/warranty/__manifest__.py +++ b/warranty/__manifest__.py @@ -10,8 +10,8 @@ 'data': [ 'security/ir.model.access.csv', + 'wizard/warranty_wizard_view.xml', 'views/warranty_views.xml', 'views/warranty_menu.xml', - 'wizard/warranty_wizard_view.xml' ] } diff --git a/warranty/models/__init__.py b/warranty/models/__init__.py index c8dacb00353..621650fa06a 100644 --- a/warranty/models/__init__.py +++ b/warranty/models/__init__.py @@ -1 +1 @@ -from . import warranty \ No newline at end of file +from . import warranty_configuration \ No newline at end of file diff --git a/warranty/models/warranty.py b/warranty/models/warranty.py deleted file mode 100644 index 3b01fff9430..00000000000 --- a/warranty/models/warranty.py +++ /dev/null @@ -1,23 +0,0 @@ -from odoo import fields, models - - -class warranty(models.Model): - _name = "warranty" - _description = "warranty model for products" - - name = fields.Char(required=True) - product_ids = fields.Many2one(comodel_name="product.product") - percentage = fields.Float(DecimalPrecision=2) - - -class ProductProduct(models.Model): - _inherit = "product.product" - - is_warranty_available = fields.Boolean() - - -class SaleOrder(models.Model): - _inherit = "sale.order" - - def add_warranty_to_product(self): - pass \ No newline at end of file diff --git a/warranty/models/warranty_configuration.py b/warranty/models/warranty_configuration.py new file mode 100644 index 00000000000..7e12145da24 --- /dev/null +++ b/warranty/models/warranty_configuration.py @@ -0,0 +1,22 @@ +from odoo import fields, models + + +class WarrantyConfiguration(models.Model): + _name = "warranty.configuration" + _description = "warranty model for products" + + name = fields.Char(required=True) + product_id = fields.Many2one(comodel_name="product.product") + percentage = fields.Float(DecimalPrecision=2) + duration = fields.Integer(string="Duration (Years)", required=True, default=1) + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + is_warranty_available = fields.Boolean(default=False, string="is warranty available") + +class SaleOrder(models.Model): + _inherit = "sale.order.line" + + warranty_id = fields.Many2one('sale.order.line', string="Warranty For", help="The original order line associated with this warranty") diff --git a/warranty/security/ir.model.access.csv b/warranty/security/ir.model.access.csv index 5b8cfb70349..1e40c91c948 100644 --- a/warranty/security/ir.model.access.csv +++ b/warranty/security/ir.model.access.csv @@ -1,4 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_warranty_manager,res.warranty.managar,model_warranty,base.group_user,1,1,1,1 -access_warranty_wizard_manager,res.warranty.wizard.managar,model_warranty_product_wizard,base.group_user,1,1,1,1 \ No newline at end of file +access_warranty_manager,res.warranty.managar,model_warranty_configuration,base.group_user,1,1,1,1 +access_warranty_wizard_manager,res.warranty.wizard.managar,model_add_warranty_wizard,base.group_user,1,1,1,1 +access_warranty_wizard_line_manager,res.warranty.wizard.line.managar,model_add_warranty_line_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/warranty/views/warranty_menu.xml b/warranty/views/warranty_menu.xml index 7682265cfec..d905dba8186 100644 --- a/warranty/views/warranty_menu.xml +++ b/warranty/views/warranty_menu.xml @@ -3,7 +3,7 @@ <!-- Define the Action --> <record id="warranty_configuration_action" model="ir.actions.act_window"> <field name="name">Warranty Configuration</field> - <field name="res_model">warranty</field> + <field name="res_model">warranty.configuration</field> <field name="view_mode">list</field> <field name="help" type="html"> <p>This action allows you to configure warranty settings.</p> diff --git a/warranty/views/warranty_views.xml b/warranty/views/warranty_views.xml index f1451c9fbe6..bf2d3000ebe 100644 --- a/warranty/views/warranty_views.xml +++ b/warranty/views/warranty_views.xml @@ -3,11 +3,11 @@ <!-- Warranty List View --> <record id="warranty_list_view" model="ir.ui.view"> <field name="name">warranty.list</field> - <field name="model">warranty</field> + <field name="model">warranty.configuration</field> <field name="arch" type="xml"> <list string="warranty list"> <field name="name" string="Name" /> - <field name="product_ids" string="Product" /> + <field name="product_id" string="Product" /> <field name="percentage" string="Percentage" /> </list> </field> @@ -31,9 +31,10 @@ <field name="inherit_id" ref="sale.view_order_form" /> <field name="arch" type="xml"> <group name="note_group" position="before"> - <button name="add_warranty_to_product" type="object" class="btn btn-secondary" + <button name="%(action_open_warranty_wizard)d" type="action" class="btn-primary " string="Add Warranty" /> </group> </field> </record> + </odoo> \ No newline at end of file diff --git a/warranty/wizard/warranty_wizard.py b/warranty/wizard/warranty_wizard.py index 81b939d4c1f..f3dc095a31d 100644 --- a/warranty/wizard/warranty_wizard.py +++ b/warranty/wizard/warranty_wizard.py @@ -1,14 +1,79 @@ from odoo import models, fields, api +from datetime import timedelta -class WarrantyProductWizard(models.TransientModel): - _name = 'warranty.product.wizard' - _description = 'Warranty Product Wizard' +class AddWarrantyWizard(models.TransientModel): + _name = 'add.warranty.wizard' + _description = 'Add Warranty Wizard' - product_id = fields.One2many("") - year = fields.Many2one(comodel_name="warranty") - end_date = fields.Date(readonly=True, compute="_compute_end_date"); + sale_order_id = fields.Many2one('sale.order.line', string="Sale Order") + warranty_line_ids = fields.One2many('add.warranty.line.wizard', 'wizard_id', string="Warranty Lines") + @api.model + def default_get(self, fields_list): + res = super(AddWarrantyWizard, self).default_get(fields_list) + active_id = self.env.context.get('active_id') + if active_id: + sale_order = self.env['sale.order'].browse(active_id) + warranty_lines = [] + for line in sale_order.order_line: + if line.product_id.is_warranty_available: + warranty_lines.append((0, 0, { + 'sale_order_line_id': line.id, + 'product_id': line.product_id.id, + })) + res['sale_order_id'] = sale_order.id + res['warranty_line_ids'] = warranty_lines + return res - @api.depends("year") + def action_add_warranty(self): + new_order_line_list = [] + + for record in self: + sale_order = record.sale_order_id + for line in record.warranty_line_ids: + if line.warranty_config_id.name: + new_order_line_list.append( + { + "order_id": sale_order.id, + "name": str(line.warranty_config_id.name) + + "/" + + str(line.end_date), + "price_unit": line.sale_order_line_id.price_subtotal + * (line.warranty_config_id.percentage / 100), + "product_id": line.warranty_config_id.product_id.id, + "warranty_id": line.sale_order_line_id.id, + "sequence": line.sale_order_line_id.sequence, + } + ) + self.env["sale.order.line"].create(new_order_line_list) + + + + +class AddWarrantyLineWizard(models.TransientModel): + _name = 'add.warranty.line.wizard' + _description = 'Add Warranty Line Wizard' + + wizard_id = fields.Many2one('add.warranty.wizard', string="Wizard") + + sale_order_line_id = fields.Many2one("sale.order.line", string = "Sale Order Line") + + product_id = fields.Many2one('product.product', compute="_compute_product_name", string="Product") + warranty_config_id = fields.Many2one('warranty.configuration', string="Year", ondelete="cascade") + end_date = fields.Date(string="End Date", compute="_compute_end_date") + + @api.depends("sale_order_line_id") + def _compute_product_name(self): + for record in self: + if record.sale_order_line_id: + record.product_id = record.sale_order_line_id.product_id + else: + record.product_id = False + + @api.depends('warranty_config_id') def _compute_end_date(self): - pass + for record in self: + if record.warranty_config_id and record.warranty_config_id.duration: + record.end_date = fields.Date.today() + timedelta(days=record.warranty_config_id.duration * 365) + else: + record.end_date = False diff --git a/warranty/wizard/warranty_wizard_view.xml b/warranty/wizard/warranty_wizard_view.xml index 2be8a55795f..ecee9c952fe 100644 --- a/warranty/wizard/warranty_wizard_view.xml +++ b/warranty/wizard/warranty_wizard_view.xml @@ -1,20 +1,33 @@ -<?xml version="1.0" encoding="utf-8"?> - <odoo> - <record id="view_warranty_product_wizard" model="ir.ui.view"> - <field name="name">warranty.product.wizard.form</field> - <field name="model">warranty.product.wizard</field> + <record id="view_add_warranty_wizard" model="ir.ui.view"> + <field name="name">add.warranty.wizard.form</field> + <field name="model">add.warranty.wizard</field> <field name="arch" type="xml"> - <form string="Warranty Products"> - <group> - <field name="product_ids" widget="many2many_tags" /> - </group> + <form string="Add Warranty" editable="false"> + <sheet> + <field name="warranty_line_ids"> + <list create="false" no_open="1" editable="top"> + <field name="sale_order_line_id" optional="hide" /> + <field name="product_id" readonly="1" /> + <field name="warranty_config_id" /> + <field name="end_date" readonly="1" /> + </list> + </field> + </sheet> <footer> - <button string="Add" type="object" name="action_display_products" + <button string="Add" name="action_add_warranty" type="object" class="btn-primary" /> - <button string="Cancel" class="btn-default" special="cancel" /> + <button string="Cancel" class="btn-secondary" special="cancel" /> </footer> </form> </field> </record> + + <record id="action_open_warranty_wizard" model="ir.actions.act_window"> + <field name="name">Add Warranty</field> + <field name="res_model">add.warranty.wizard</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_add_warranty_wizard" /> + <field name="target">new</field> + </record> </odoo> \ No newline at end of file From 44cc5d1d679fd3eda554c24f666a7d07fa42834c Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Fri, 6 Dec 2024 18:24:03 +0530 Subject: [PATCH 11/16] [FIX] warranty: Fixed sale order line warranty configuration The fixed code is the warranty button that is connected to the product.product model, it was creating an issue, and now it is connected to the product.template created different files for the wizard line for better separation of code and ease of understanding. The issue fixed related to the field mismatch of the name issue is fixed now. --- warranty/models/warranty_configuration.py | 13 ++-- warranty/views/warranty_views.xml | 5 +- warranty/wizard/__init__.py | 3 +- warranty/wizard/warranty_wizard.py | 82 +++++++---------------- warranty/wizard/warranty_wizard_line.py | 34 ++++++++++ 5 files changed, 71 insertions(+), 66 deletions(-) create mode 100644 warranty/wizard/warranty_wizard_line.py diff --git a/warranty/models/warranty_configuration.py b/warranty/models/warranty_configuration.py index 7e12145da24..b376bd93a41 100644 --- a/warranty/models/warranty_configuration.py +++ b/warranty/models/warranty_configuration.py @@ -1,22 +1,21 @@ from odoo import fields, models - class WarrantyConfiguration(models.Model): _name = "warranty.configuration" _description = "warranty model for products" - name = fields.Char(required=True) - product_id = fields.Many2one(comodel_name="product.product") - percentage = fields.Float(DecimalPrecision=2) - duration = fields.Integer(string="Duration (Years)", required=True, default=1) + name = fields.Char(required=True, string="Name") + product_id = fields.Many2one(comodel_name="product.template", string="Product") + percentage = fields.Float(DecimalPrecision=2, default=0.0, required=True) + duration = fields.Integer(string="Duration (Years)", required=True, default=0) class ProductTemplate(models.Model): _inherit = "product.template" is_warranty_available = fields.Boolean(default=False, string="is warranty available") - + class SaleOrder(models.Model): _inherit = "sale.order.line" - warranty_id = fields.Many2one('sale.order.line', string="Warranty For", help="The original order line associated with this warranty") + warranty_id = fields.Many2one('sale.order.line', string="Warranty For", help="The original order line associated with this warranty", ondelete="cascade") diff --git a/warranty/views/warranty_views.xml b/warranty/views/warranty_views.xml index bf2d3000ebe..29c49d07994 100644 --- a/warranty/views/warranty_views.xml +++ b/warranty/views/warranty_views.xml @@ -15,8 +15,8 @@ <record id="is_warranty_available_button" model="ir.ui.view"> <field name="name">product.template.is.warranty.available</field> - <field name="model">product.product</field> - <field name="inherit_id" ref="product.product_normal_form_view" /> + <field name="model">product.template</field> + <field name="inherit_id" ref="product.product_template_form_view" /> <field name="arch" type="xml"> <field name="product_tag_ids" position="after"> <field name="is_warranty_available" @@ -25,6 +25,7 @@ </field> </record> + <!-- Warranty Butoton --> <record id="add_warranty_button_cust" model="ir.ui.view"> <field name="name">sale.order.add.warranty.button.cust</field> <field name="model">sale.order</field> diff --git a/warranty/wizard/__init__.py b/warranty/wizard/__init__.py index 4ee22aff5b8..f3cc57315b8 100644 --- a/warranty/wizard/__init__.py +++ b/warranty/wizard/__init__.py @@ -1 +1,2 @@ -from . import warranty_wizard \ No newline at end of file +from . import warranty_wizard +from . import warranty_wizard_line \ No newline at end of file diff --git a/warranty/wizard/warranty_wizard.py b/warranty/wizard/warranty_wizard.py index f3dc095a31d..4061190c787 100644 --- a/warranty/wizard/warranty_wizard.py +++ b/warranty/wizard/warranty_wizard.py @@ -1,79 +1,49 @@ from odoo import models, fields, api -from datetime import timedelta class AddWarrantyWizard(models.TransientModel): _name = 'add.warranty.wizard' _description = 'Add Warranty Wizard' - sale_order_id = fields.Many2one('sale.order.line', string="Sale Order") + sale_order_id = fields.Many2one('sale.order', string="Sale Order") # Corrected to point to sale.order warranty_line_ids = fields.One2many('add.warranty.line.wizard', 'wizard_id', string="Warranty Lines") @api.model - def default_get(self, fields_list): - res = super(AddWarrantyWizard, self).default_get(fields_list) + def default_get(self, fields): + res = super(AddWarrantyWizard, self).default_get(fields) active_id = self.env.context.get('active_id') if active_id: sale_order = self.env['sale.order'].browse(active_id) + if not sale_order: + return res + warranty_lines = [] for line in sale_order.order_line: - if line.product_id.is_warranty_available: + if line.product_template_id and line.product_template_id.is_warranty_available: warranty_lines.append((0, 0, { 'sale_order_line_id': line.id, - 'product_id': line.product_id.id, + 'product_id': line.product_template_id.id, })) - res['sale_order_id'] = sale_order.id - res['warranty_line_ids'] = warranty_lines + + res.update({ + 'sale_order_id': sale_order.id, + 'warranty_line_ids': warranty_lines, + }) return res def action_add_warranty(self): new_order_line_list = [] - for record in self: - sale_order = record.sale_order_id + sale_order = record.sale_order_id for line in record.warranty_line_ids: - if line.warranty_config_id.name: - new_order_line_list.append( - { - "order_id": sale_order.id, - "name": str(line.warranty_config_id.name) - + "/" - + str(line.end_date), - "price_unit": line.sale_order_line_id.price_subtotal - * (line.warranty_config_id.percentage / 100), - "product_id": line.warranty_config_id.product_id.id, - "warranty_id": line.sale_order_line_id.id, - "sequence": line.sale_order_line_id.sequence, - } - ) - self.env["sale.order.line"].create(new_order_line_list) - - - - -class AddWarrantyLineWizard(models.TransientModel): - _name = 'add.warranty.line.wizard' - _description = 'Add Warranty Line Wizard' - - wizard_id = fields.Many2one('add.warranty.wizard', string="Wizard") - - sale_order_line_id = fields.Many2one("sale.order.line", string = "Sale Order Line") - - product_id = fields.Many2one('product.product', compute="_compute_product_name", string="Product") - warranty_config_id = fields.Many2one('warranty.configuration', string="Year", ondelete="cascade") - end_date = fields.Date(string="End Date", compute="_compute_end_date") - - @api.depends("sale_order_line_id") - def _compute_product_name(self): - for record in self: - if record.sale_order_line_id: - record.product_id = record.sale_order_line_id.product_id - else: - record.product_id = False - - @api.depends('warranty_config_id') - def _compute_end_date(self): - for record in self: - if record.warranty_config_id and record.warranty_config_id.duration: - record.end_date = fields.Date.today() + timedelta(days=record.warranty_config_id.duration * 365) - else: - record.end_date = False + if line.warranty_config_id: + new_order_line_list.append({ + "order_id": sale_order.id, + "name": f"{line.warranty_config_id.name} / {line.end_date}", + "product_id": line.warranty_config_id.product_id.product_variant_id.id, + "price_unit": line.sale_order_line_id.price_subtotal + * (line.warranty_config_id.percentage / 100), + "warranty_id": line.sale_order_line_id.id, + "sequence": line.sale_order_line_id.sequence, + }) + + res = self.env["sale.order.line"].create(new_order_line_list) diff --git a/warranty/wizard/warranty_wizard_line.py b/warranty/wizard/warranty_wizard_line.py new file mode 100644 index 00000000000..109ab3f01a5 --- /dev/null +++ b/warranty/wizard/warranty_wizard_line.py @@ -0,0 +1,34 @@ +from odoo import models, fields, api +from datetime import timedelta + + +class AddWarrantyLineWizard(models.TransientModel): + _name = 'add.warranty.line.wizard' + _description = 'Add Warranty Line Wizard' + + wizard_id = fields.Many2one('add.warranty.wizard', string="Wizard") + + sale_order_line_id = fields.Many2one("sale.order.line", string = "Sale Order Line") + + product_id = fields.Many2one( + "product.template", compute="_compute_product_name", string="Product" + ) + + warranty_config_id = fields.Many2one('warranty.configuration', string="Year") + end_date = fields.Date(string="End Date", compute="_compute_end_date") + + @api.depends("sale_order_line_id") + def _compute_product_name(self): + for record in self: + if record.sale_order_line_id: + record.product_id = record.sale_order_line_id.product_template_id + else: + record.product_id = False + + @api.depends('warranty_config_id') + def _compute_end_date(self): + for record in self: + if record.warranty_config_id and record.warranty_config_id.duration: + record.end_date = fields.Date.today() + timedelta(days=record.warranty_config_id.duration * 365) + else: + record.end_date = False From 6401707a83503d36d7061756d033a85b2125c837 Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Mon, 9 Dec 2024 11:09:10 +0530 Subject: [PATCH 12/16] [FIX] estate: fixed variable name convention issue Changed the name of the variable as per naming convention in odoo. --- estate/models/estate_property.py | 4 ++-- estate/models/estate_property_offer.py | 2 +- estate/views/estate_property_views.xml | 2 +- estate/wizard/estate_property_offer_wizard.py | 4 ++-- estate/wizard/estate_property_offer_wizard_view.xml | 2 +- estate_account/models/estate_property.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 927d3aef27b..08b6325ef81 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -28,7 +28,7 @@ class estate_property(models.Model): garden_orientation = fields.Selection( selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]) salesman_id = fields.Many2one("res.users", string='Salesperson', default=lambda self: self.env.user) - buyer = fields.Many2one("res.partner", copy=False) + buyer_id = fields.Many2one("res.partner", copy=False) property_type_id = fields.Many2one("estate.property.type") tag_ids = fields.Many2many("estate.property.tag") offer_ids = fields.One2many(comodel_name="estate.property.offer", inverse_name="property_id", string= "Offers") @@ -99,7 +99,7 @@ def _check_selling_price(self): ("The selling price (%.2f) cannot be lower than 90%% of the expected price (%.2f).") % (record.selling_price, min_price) ) - + @api.ondelete(at_uninstall=False) def _check_deletion_state(self): diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b36c8556bb9..986721e32c3 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -41,7 +41,7 @@ def _inverse_date_deadline(self): def action_accept_offer(self): for record in self: record.status = 'accepted' - record.property_id.buyer = record.partner_id + record.property_id.buyer_id = record.partner_id record.property_id.selling_price = record.price return True diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5abaa813565..a6b23d3d3c5 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -205,7 +205,7 @@ <page string="Other Info"> <group> <field name="salesman_id" class="mb16" /> - <field name="buyer" class="mb16" /> + <field name="buyer_id" class="mb16" /> </group> </page> </notebook> diff --git a/estate/wizard/estate_property_offer_wizard.py b/estate/wizard/estate_property_offer_wizard.py index 3299a07cf07..fb0f34a58b7 100644 --- a/estate/wizard/estate_property_offer_wizard.py +++ b/estate/wizard/estate_property_offer_wizard.py @@ -6,7 +6,7 @@ class estate_property_offer_wizard(models.TransientModel): price = fields.Float() validity = fields.Integer(default=7) - buyer = fields.Many2one("res.partner", copy=False) + buyer_id = fields.Many2one("res.partner", copy=False) def make_an_offer(self): properties = self.env['estate.property'].browse(self._context.get("records", [])) @@ -15,7 +15,7 @@ def make_an_offer(self): 'price': self.price, 'property_id': property.id, 'validity': self.validity, - 'partner_id': self.buyer.id, + 'partner_id': self.buyer_id.id, }) return {'type': 'ir.actions.act_window_close'} \ No newline at end of file diff --git a/estate/wizard/estate_property_offer_wizard_view.xml b/estate/wizard/estate_property_offer_wizard_view.xml index 4c1b2f79df1..299410041b9 100644 --- a/estate/wizard/estate_property_offer_wizard_view.xml +++ b/estate/wizard/estate_property_offer_wizard_view.xml @@ -10,7 +10,7 @@ <group> <field name="price"></field> <field name="validity"></field> - <field name="buyer"></field> + <field name="buyer_id"></field> </group> </sheet> <footer> diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 14fc3c98632..984ba6af0dd 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -15,7 +15,7 @@ def action_set_property_as_sold(self): move_values = { - 'partner_id': self.buyer.id, + 'partner_id': self.buyer_id.id, 'move_type': 'out_invoice', 'property_id':self.id, 'invoice_line_ids': [ From 4d7fd3caf06543f6a13340ac43f82327d249c369 Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Wed, 11 Dec 2024 18:42:05 +0530 Subject: [PATCH 13/16] [ADD] budget: Added code for new app budget Created models for Budget and Budget Line. Created wizard (transient model, views, and action) for adding new budgets based on a selected time period. Developed Kanban and form views for the budget model & list view for budget line. Updated actions to allow opening budget lines directly from Kanban boxes. Implemented views and menu items for the Budget and Budget Line models. Added restriction on add analtic line based on users selected preferece Inherited list view of account analytic line to make it editable and creatable Added action for open analytical lines on click on view button. Developed graph and pivot views for budget. Added constraints on write, create form view of budget , analytic line and budget line as per requirement. Updated float to monatry field for shwoing currency. --- budget/__init__.py | 2 + budget/__manifest__.py | 21 +++ budget/models/__init__.py | 3 + budget/models/account_analytic_line.py | 38 ++++++ budget/models/budget.py | 104 ++++++++++++++ budget/models/budget_line.py | 27 ++++ budget/security/ir.model.access.csv | 5 + budget/views/budget_line_view.xml | 18 +++ budget/views/budget_menu_view.xml | 36 +++++ budget/views/budget_views.xml | 182 +++++++++++++++++++++++++ budget/wizard/__init__.py | 1 + budget/wizard/budget_wizard.py | 58 ++++++++ budget/wizard/budget_wizard_view.xml | 35 +++++ 13 files changed, 530 insertions(+) create mode 100644 budget/__init__.py create mode 100644 budget/__manifest__.py create mode 100644 budget/models/__init__.py create mode 100644 budget/models/account_analytic_line.py create mode 100644 budget/models/budget.py create mode 100644 budget/models/budget_line.py create mode 100644 budget/security/ir.model.access.csv create mode 100644 budget/views/budget_line_view.xml create mode 100644 budget/views/budget_menu_view.xml create mode 100644 budget/views/budget_views.xml create mode 100644 budget/wizard/__init__.py create mode 100644 budget/wizard/budget_wizard.py create mode 100644 budget/wizard/budget_wizard_view.xml diff --git a/budget/__init__.py b/budget/__init__.py new file mode 100644 index 00000000000..c536983e2b2 --- /dev/null +++ b/budget/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard \ No newline at end of file diff --git a/budget/__manifest__.py b/budget/__manifest__.py new file mode 100644 index 00000000000..79b6d981a98 --- /dev/null +++ b/budget/__manifest__.py @@ -0,0 +1,21 @@ +{ + 'name': 'Budget', + 'version': '1.0', + 'author': 'DhruvKumar Nagar', + 'summary': 'Manage budgets effectively', + 'description': """ + A custom module for creating and managing budgets in Odoo. + """, + 'depends': ['base', 'account'], + 'data': [ + 'security/ir.model.access.csv', + 'views/budget_line_view.xml', + 'views/budget_menu_view.xml', + 'views/budget_views.xml', + 'wizard/budget_wizard_view.xml' + ], + 'installable': True, + 'application': True, + 'auto_install': False, + "license": "LGPL-3" +} diff --git a/budget/models/__init__.py b/budget/models/__init__.py new file mode 100644 index 00000000000..9f682b84f24 --- /dev/null +++ b/budget/models/__init__.py @@ -0,0 +1,3 @@ +from . import budget +from . import budget_line +from . import account_analytic_line \ No newline at end of file diff --git a/budget/models/account_analytic_line.py b/budget/models/account_analytic_line.py new file mode 100644 index 00000000000..247680dad75 --- /dev/null +++ b/budget/models/account_analytic_line.py @@ -0,0 +1,38 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + + budget_line_id = fields.Many2one(comodel_name="budget.management.budget.lines") + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + budget_line = self.env["budget.management.budget.lines"].browse( + vals.get("budget_line_id") + ) + budget = budget_line.budget_id + if budget.on_over_budget == "restriction": + if sum(budget_line.analytic_line_ids.mapped("amount"))+vals.get("amount") > budget_line.budget_amount: + raise ValidationError( + "You cannot create a budget line because it exceeds the allowed budget!" + ) + return super(AccountAnalyticLine, self).create(vals_list) + + def write(self, vals): + if "amount" in vals: + for record in self: + old_amount = record.amount + new_amount = vals.get("amount") + print(old_amount,new_amount) + total_amount = sum(record.budget_line_id.analytic_line_ids.mapped("amount")) + new_amount - old_amount + + budget_line = record.budget_line_id + budget = budget_line.budget_id + if budget.on_over_budget == "restriction" and total_amount > budget_line.budget_amount: + raise ValidationError( + "You cannot update this budget line because it exceeds the allowed budget!" + ) + return super(AccountAnalyticLine, self).write(vals) diff --git a/budget/models/budget.py b/budget/models/budget.py new file mode 100644 index 00000000000..55d24c22719 --- /dev/null +++ b/budget/models/budget.py @@ -0,0 +1,104 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError, UserError + + +class Budget(models.Model): + _name = "budget.budget" + _inherit = ["mail.thread", "mail.activity.mixin"] + _description = " " + + name = fields.Char(compute="_compute_budget_name", store=True, readonly=True) + active = fields.Boolean(default=True) + is_favorite = fields.Boolean(default=False) + color = fields.Integer(string="Color Index") + state = fields.Selection( + selection=[ + ("draft", "Draft"), + ("confirmed", "Confirmed"), + ("revised", "Revised"), + ("done", "Done"), + ], + required=True, + default="draft", + tracking=True, + ) + on_over_budget = fields.Selection( + selection=[("warning", "Warning"), ("restriction", "Restriction")], + tracking=True, + ) + responsible = fields.Many2one( + comodel_name="res.users", # Assuming you want a link to Odoo users + string="Responsible", + tracking=True, + ) + revision_id = fields.Many2one( + comodel_name="res.users", # Assuming you want a link to Odoo users + tracking=True, + readonly=True, + ) + date_start = fields.Date(string="Start Date", required=True) + date_end = fields.Date(string="Expiration Date", required=True, index=True) + company_id = fields.Many2one( + "res.company", + string="Company", + default=lambda self: self.env.company, + ) + budget_line_ids = fields.One2many( + comodel_name="budget.management.budget.lines", inverse_name="budget_id" + ) + warnings = fields.Text(readonly=True) + currency_id = fields.Many2one( + comodel_name="res.currency", + string="Currency", + required=True, + default=lambda self: self.env.company.currency_id, + ) + + @api.depends("date_start", "date_end") + def _compute_budget_name(self): + for record in self: + if record.date_start and record.date_end: + start_date = record.date_start.strftime("%Y-%m") + end_date = record.date_end.strftime("%Y-%m") + record.name = f"Budget {start_date} to {end_date}" + else: + record.name = "Unknown Budget" + + def onclick_reset_to_draft(self): + for record in self: + if record.state != "draft": + record.state = "draft" + + def onclick_confirmed(self): + for record in self: + if record.state == "draft": + record.state = "confirmed" + + def onclick_revise(self): + for record in self: + if record.state != "confirmed": + raise UserError("Only confirmed budgets can be revised.") + if record.state in ["confirmed"]: + record.revision_id = self.env.user + record.state = "revised" + + def onclick_done(self): + for record in self: + if record.state in ["confirmed", "revised"]: + record.state = "done" + + @api.constrains("date_start", "date_end") + def _check_period_overlap(self): + for record in self: + overlapping_budgets = self.search( + [ + ("id", "!=", record.id), + ("date_start", "<=", record.date_start), + ("date_end", ">=", record.date_end), + ("company_id", "=", record.company_id.id), + ] + ) + if overlapping_budgets: + raise ValidationError( + "Cannot create overlapping budgets for the same period and company." + ) diff --git a/budget/models/budget_line.py b/budget/models/budget_line.py new file mode 100644 index 00000000000..44ce5718ab8 --- /dev/null +++ b/budget/models/budget_line.py @@ -0,0 +1,27 @@ +from odoo import models, fields, api + +class BudgetLine(models.Model): + _name = "budget.management.budget.lines" + + name = fields.Char() + budget_id = fields.Many2one("budget.budget", string="Budget") + budget_amount = fields.Float(default=0.0) + achieved_amount = fields.Float(default=0.0) + achieved_percentage = fields.Float( + default=0.0, + compute="_compute_achieved_percentage", + store=True + ) + analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account') + # analytic_line_ids = fields.One2many('account.analytic.line', string='Analytic Account') + date_start = fields.Date(string="Start Date", required=True) + date_end = fields.Date(string="End Date", required=False) + + @api.depends("budget_amount", "achieved_amount") + def _compute_achieved_percentage(self): + for record in self: + if record.budget_amount: + record.achieved_percentage = (record.achieved_amount / record.budget_amount) * 100 + else: + record.achieved_percentage = 0.0 + diff --git a/budget/security/ir.model.access.csv b/budget/security/ir.model.access.csv new file mode 100644 index 00000000000..873e5995949 --- /dev/null +++ b/budget/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + +access_budget_line,budget.line,model_budget_management_budget_lines,,1,1,1,1 +access_budget,budget,model_budget_budget,,1,1,1,1 +access_budget_wizard,budget.wizard,model_add_budget_wizard,,1,1,1,1 diff --git a/budget/views/budget_line_view.xml b/budget/views/budget_line_view.xml new file mode 100644 index 00000000000..8e17fae3f26 --- /dev/null +++ b/budget/views/budget_line_view.xml @@ -0,0 +1,18 @@ +<odoo> + <record id="view_budget_management_budget_line_tree" model="ir.ui.view"> + <field name="name">budget.line.tree</field> + <field name="model">budget.management.budget.lines</field> + <field name="arch" type="xml"> + <list string="Budget Lines" + default_order="id desc" + sample="1" + editable="bottom" + > + <field string="Budget" name="name" /> + <field string="Analytic Account" name="analytic_account_id" /> + <field string="Budget Amount" name="budget_amount" /> + <field string="Archived Amount" name="achieved_amount" /> + </list> + </field> + </record> +</odoo> \ No newline at end of file diff --git a/budget/views/budget_menu_view.xml b/budget/views/budget_menu_view.xml new file mode 100644 index 00000000000..a64f9fa5eb7 --- /dev/null +++ b/budget/views/budget_menu_view.xml @@ -0,0 +1,36 @@ +<odoo> + <record id="action_budget_management_menu_budget" model="ir.actions.act_window"> + <field name="name">Budgets</field> + <field name="res_model">budget.budget</field> + <field name="view_mode">kanban,form</field> + <field name="help" type="html"> + <p class="o_view_nocontent_smiling_face"> + No records available. + </p> + <p> + Please check your filters or create new records. + </p> + </field> + </record> + + <record id="action_budget_management_budget_line" model="ir.actions.act_window"> + <field name="name">Budgets Lines</field> + <field name="res_model">budget.management.budget.lines</field> + <field name="view_mode">list</field> + <field name="view_id" ref="budget.view_budget_management_budget_line_tree" /> + <field name="context">{'default_budget_id': active_id}</field> + <field name="domain">[('budget_id', '=', context.get('default_budget_id'))]</field> + </record> + + <menuitem + id="budget_management_menu" + name="Budget" + /> + + <menuitem + id="budget_management_menu_budget" + name="Budgets" + parent="budget_management_menu" + action="action_budget_management_menu_budget" + /> +</odoo> \ No newline at end of file diff --git a/budget/views/budget_views.xml b/budget/views/budget_views.xml new file mode 100644 index 00000000000..c3e454ce42b --- /dev/null +++ b/budget/views/budget_views.xml @@ -0,0 +1,182 @@ +<odoo> + <record id="view_budget_management_kanban" model="ir.ui.view"> + <field name="name">budget.budget.kanban</field> + <field name="model">budget.budget</field> + <field name="arch" type="xml"> + <kanban sample="1" + can_open="0" + type="action" + action="%(action_budget_management_budget_line)d" + on_create="budget.open_create_multiple_budget" + highlight_color="color" + > + <templates> + <t t-name="menu"> + <div class="container"> + <div class="row"> + <div name="card_menu_view"> + <div role="menuitem" aria-haspopup="true"> + <a t-if="widget.editable" role="menuitem" type="set_cover" + class="dropdown-item" data-field="displayed_image_id">Set + Cover Image</a> + <a class="dropdown-item" role="menuitem" + name="action_budget_management_menu_budget" type="open"> + Configuration</a> + <field name="color" widget="kanban_color_picker" /> + </div> + </div> + </div> + </div> + </t> + <t t-name="card"> + <div + style="display:flex;justify-content:space-between;flex-direction:column"> + <div style="display:flex;"> + <field name="is_favorite" widget="project_is_favorite" nolabel="1" /> + <h3> + <field name="name" /> + </h3> + </div> + <div style="display:flex;"> + <div t-if="record.date_end.raw_value or record.date_start.raw_value" + class="text-muted"> + <span class="fa fa-clock-o me-2" title="Dates"></span> + <field name="date_start" /> + <i + t-if="record.date_end.raw_value and record.date_start.raw_value" + class="fa fa-long-arrow-right mx-2 oe_read_only" + aria-label="Arrow icon" title="Arrow" /> + <field name="date_end" /> + </div> + </div> + </div> + <footer class="mt-auto pt-0 ms-1"> + <div class="d-flex align-items-center"> + + </div> + <div class="d-flex ms-auto align-items-center"> + <field name="responsible" widget="many2one_avatar_user" + domain="[('share', '=', False)]" class="me-1" /> + </div> + </footer> + </t> + </templates> + </kanban> + </field> + </record> + + + <record id="view_budget_management_form" model="ir.ui.view"> + <field name="name">budget.budget.form</field> + <field name="model">budget.budget</field> + <field name="arch" type="xml"> + <form string="Test"> + <header> + <button name="onclick_reset_to_draft" type="object" string="Reset to Draft" + invisible="state in ['draft']" + /> + <button name="onclick_revise" type="object" string="Revise" + invisible="state in ['revise','done']" + /> + <button name="onclick_done" type="object" string="Done" + invisible="state in ['done']" + /> + <field name="state" widget="statusbar" + statusbar_visible="draft,confirmed,revised,done" /> + </header> + <sheet> + <h1> + <field name="name" /> + </h1> + <field name="active" invisible="True" /> + <group> + <group> + <field name="responsible" /> + </group> + <group> + <field name="date_start" string="Period" widget="daterange" + options="{'end_date_field': 'date_end', 'always_range': '1'}" + optional="hide" /> + </group> + </group> + <group> + <group> + <field name="company_id" /> + </group> + <group> + <field name="on_over_budget" /> + </group> + </group> + <group> + <group> + <field name="revision_id" /> + </group> + </group> + <notebook> + <page name="Budget Lines"> + <field name="budget_line_ids"> + <list> + <field name="name" string="Analytic Account" /> + <field name="budget_amount" string="Budget Amount" /> + <field name="achieved_amount" string="Archived Amount" /> + <field name="achieved_percentage" string="Archived (%)" + widget="progressbar" options="{'max_value': 100}" /> + </list> + </field> + </page> + </notebook> + </sheet> + <chatter /> + </form> + </field> + </record> + + <record id="view_budget_lines_graph" model="ir.ui.view"> + <field name="name">budget.lines.graph</field> + <field name="model">budget.management.budget.lines</field> + <field name="arch" type="xml"> + <graph string="Budget Line Analysis" type="bar"> + <field name="budget_id" type="row" /> + <field name="achieved_percentage" type="measure" /> + <field name="analytic_account_id" type="col" /> + </graph> + </field> + </record> + + <record id="view_budget_lines_pivot" model="ir.ui.view"> + <field name="name">budget.lines.pivot</field> + <field name="model">budget.management.budget.lines</field> + <field name="arch" type="xml"> + <pivot string="Budget Line Pivot"> + <field name="budget_id" type="row" /> + <field name="analytic_account_id" type="col" /> + <field name="budget_amount" type="measure" /> + <field name="achieved_amount" type="measure" /> + <field name="achieved_percentage" type="measure" /> + </pivot> + </field> + </record> + + <record id="view_budget_lines_gantt" model="ir.ui.view"> + <field name="name">budget.lines.gantt</field> + <field name="model">budget.management.budget.lines</field> + <field name="arch" type="xml"> + <gantt string="Budget Lines Timeline" date_start="date_start" date_stop="date_end"> + <field name="name" /> + <field name="budget_amount" sum="Total Budget" /> + </gantt> + </field> + </record> + + <record id="action_budget_lines" model="ir.actions.act_window"> + <field name="name">Budget Lines</field> + <field name="res_model">budget.management.budget.lines</field> + <field name="view_mode">graph,pivot,gantt</field> + <field name="context">{}</field> + </record> + + <menuitem id="menu_budget_lines" name="Budget Lines" + parent="budget_management_menu" action="action_budget_lines" /> + + +</odoo> \ No newline at end of file diff --git a/budget/wizard/__init__.py b/budget/wizard/__init__.py new file mode 100644 index 00000000000..b70dd08917a --- /dev/null +++ b/budget/wizard/__init__.py @@ -0,0 +1 @@ +from . import budget_wizard \ No newline at end of file diff --git a/budget/wizard/budget_wizard.py b/budget/wizard/budget_wizard.py new file mode 100644 index 00000000000..a8a6e8bcfc4 --- /dev/null +++ b/budget/wizard/budget_wizard.py @@ -0,0 +1,58 @@ +from odoo import models, fields +from datetime import timedelta +from dateutil.relativedelta import relativedelta + + +class AddBudgetWizard(models.TransientModel): + _name = "add.budget.wizard" + + date_start = fields.Date(required=True, string="Start Date") + date_end = fields.Date( + string="Expiration Date", + required=True, + index=True, + ) + + periods = fields.Selection( + selection=[("monthly", "Monthly"), ("quarterly", "Quarterly")], + required=True, + default="monthly", + ) + + analytic_account_ids = fields.Many2many( + "account.analytic.account", string="Analytic Account" + ) + + # analytic_account = fields.Many2many(comodel_name="account.analytic.account") + def action_add_budget(self): + """Creates budget records based on the selected periods.""" + if self.date_start >= self.date_end: + raise ValueError("Start Date must be before Expiration Date.") + + # Calculate the periods and create budgets + current_date = self.date_start + budget_entries = [] + + while current_date <= self.date_end: + next_date = ( + current_date + relativedelta(months=1) + if self.periods == "monthly" + else current_date + relativedelta(months=3) + ) + end_date = min(next_date - timedelta(days=1), self.date_end) + + budget_entries.append( + { + "name": f"Budget {current_date.strftime('%Y-%m')} to {end_date.strftime('%Y-%m')}", + "date_start": current_date, + "date_end": end_date, + # "budget_line_ids": self.analytic_account_ids, + } + ) + + current_date = next_date + print(budget_entries) + self.env["budget.budget"].create(budget_entries) + + # Return a window close action + return {"type": "ir.actions.act_window_close"} diff --git a/budget/wizard/budget_wizard_view.xml b/budget/wizard/budget_wizard_view.xml new file mode 100644 index 00000000000..a74e96d4a55 --- /dev/null +++ b/budget/wizard/budget_wizard_view.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + <record id="view_add_budget_wizard" model="ir.ui.view"> + <field name="name">add.budget.wizard.form</field> + <field name="model">add.budget.wizard</field> + <field name="arch" type="xml"> + <form string="Add Offer"> + <sheet> + <group> + <field name="date_start" string="Planned Date" widget="daterange" + options="{'end_date_field': 'date_end', 'always_range': '1'}" + optional="hide" /> + <field name="date_end" invisible="True" /> + <field name="periods" widget="radio" /> + <field name="analytic_account_ids" widget="many2many_tags" /> + </group> + </sheet> + <footer> + <button string="Create" name="action_add_budget" type="object" + class="btn-primary" default_focus="1" /> + <button string="Discard" class="btn-secondary" special="cancel" /> + </footer> + </form> + </field> + </record> + <record id="open_create_multiple_budget" model="ir.actions.act_window"> + <field name="name">Create Multiple Budgets</field> + <field name="res_model">add.budget.wizard</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_add_budget_wizard" /> + <field name="target">new</field> + </record> + </data> +</odoo> \ No newline at end of file From 58335907cd2aaf2cbc14ec0f0ae843adec033ff8 Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Thu, 12 Dec 2024 18:37:00 +0530 Subject: [PATCH 14/16] [ADD] budget: Added code for analytic account views Added code analytic account line and that reference module and did code for budget review options in the menu now it's able to create duplicate and archived the old budget. Added view for analytic view changed the code structure files and moved code of graphs in the budget line module. --- budget/__manifest__.py | 2 +- budget/models/budget.py | 78 ++++++++++++------ budget/models/budget_line.py | 110 +++++++++++++++++++++---- budget/views/account_analytic_view.xml | 15 ++++ budget/views/budget_line_view.xml | 52 +++++++++++- budget/views/budget_menu_view.xml | 8 ++ budget/views/budget_views.xml | 84 ++++++------------- budget/wizard/budget_wizard.py | 2 - 8 files changed, 249 insertions(+), 102 deletions(-) create mode 100644 budget/views/account_analytic_view.xml diff --git a/budget/__manifest__.py b/budget/__manifest__.py index 79b6d981a98..cacfc76c51d 100644 --- a/budget/__manifest__.py +++ b/budget/__manifest__.py @@ -11,8 +11,8 @@ 'security/ir.model.access.csv', 'views/budget_line_view.xml', 'views/budget_menu_view.xml', + 'wizard/budget_wizard_view.xml', 'views/budget_views.xml', - 'wizard/budget_wizard_view.xml' ], 'installable': True, 'application': True, diff --git a/budget/models/budget.py b/budget/models/budget.py index 55d24c22719..d7873509acc 100644 --- a/budget/models/budget.py +++ b/budget/models/budget.py @@ -1,11 +1,10 @@ from odoo import models, fields, api from odoo.exceptions import ValidationError, UserError - class Budget(models.Model): _name = "budget.budget" _inherit = ["mail.thread", "mail.activity.mixin"] - _description = " " + _description = "Budget Management" name = fields.Char(compute="_compute_budget_name", store=True, readonly=True) active = fields.Boolean(default=True) @@ -27,12 +26,12 @@ class Budget(models.Model): tracking=True, ) responsible = fields.Many2one( - comodel_name="res.users", # Assuming you want a link to Odoo users + comodel_name="res.users", string="Responsible", tracking=True, ) revision_id = fields.Many2one( - comodel_name="res.users", # Assuming you want a link to Odoo users + comodel_name="budget.budget", # This should point to the same budget model tracking=True, readonly=True, ) @@ -64,6 +63,22 @@ def _compute_budget_name(self): else: record.name = "Unknown Budget" + @api.constrains("date_start", "date_end") + def _check_period_overlap(self): + for record in self: + overlapping_budgets = self.search( + [ + ("id", "!=", record.id), + ("date_start", "<=", record.date_start), + ("date_end", ">=", record.date_end), + ("company_id", "=", record.company_id.id), + ] + ) + if overlapping_budgets: + raise ValidationError( + "Cannot create overlapping budgets for the same period and company." + ) + def onclick_reset_to_draft(self): for record in self: if record.state != "draft": @@ -78,27 +93,44 @@ def onclick_revise(self): for record in self: if record.state != "confirmed": raise UserError("Only confirmed budgets can be revised.") - if record.state in ["confirmed"]: - record.revision_id = self.env.user - record.state = "revised" + + # Archive the current record and set its state to 'revised' + record.sudo().write({ + 'state': 'revised', + 'active': False, # Archive the original record + 'revision_id': record.id, # Set the revision_id to refer to the original budget + }) + + # Create a duplicate budget (this will be the revised budget) + duplicate = record.copy() + + # Manually copy related budget lines to the new budget (duplicate) + for line in record.budget_line_ids: + self.env["budget.management.budget.lines"].create({ + 'name': line.name, + 'budget_id': duplicate.id, # Link to the duplicated budget + 'budget_amount': line.budget_amount, + 'analytic_account_id': line.analytic_account_id.id, + # Add any other necessary fields here + }) + + # Update the duplicate record's state and fields + duplicate.sudo().write({ + 'state': 'draft', # Set the duplicate budget's state to draft + 'active': True, # Set the new record as active + 'responsible': self.env.user.id, # Set the current user as responsible for the revised budget + 'revision_id': record.id, # Set the revision_id to point to the original budget (parent budget) + 'name': f"{record.name} (Revised)", # Adjust the name for clarity + }) + + # Log a message for traceability + record.message_post( + body="The budget has been revised. A new draft budget has been created.", + message_type="notification", + ) + def onclick_done(self): for record in self: if record.state in ["confirmed", "revised"]: record.state = "done" - - @api.constrains("date_start", "date_end") - def _check_period_overlap(self): - for record in self: - overlapping_budgets = self.search( - [ - ("id", "!=", record.id), - ("date_start", "<=", record.date_start), - ("date_end", ">=", record.date_end), - ("company_id", "=", record.company_id.id), - ] - ) - if overlapping_budgets: - raise ValidationError( - "Cannot create overlapping budgets for the same period and company." - ) diff --git a/budget/models/budget_line.py b/budget/models/budget_line.py index 44ce5718ab8..c5fb8e72f54 100644 --- a/budget/models/budget_line.py +++ b/budget/models/budget_line.py @@ -1,27 +1,101 @@ from odoo import models, fields, api +from odoo.exceptions import ValidationError, UserError class BudgetLine(models.Model): _name = "budget.management.budget.lines" + _description = "Budget Management Budget Lines" - name = fields.Char() - budget_id = fields.Many2one("budget.budget", string="Budget") - budget_amount = fields.Float(default=0.0) - achieved_amount = fields.Float(default=0.0) + name = fields.Char(string="Name") + budget_id = fields.Many2one( + comodel_name="budget.budget", string="Budget", required=True + ) + state = fields.Selection(related="budget_id.state") + budget_amount = fields.Monetary( + string="Budget Amount", + default=0.0, + currency_field="currency_id", + help="The total allocated budget for this budget line.", + ) + achieved_amount = fields.Monetary( + string="Achieved Amount", + default=0.0, + compute="_compute_achieved_amount", + store=True, + currency_field="currency_id", + ) achieved_percentage = fields.Float( - default=0.0, - compute="_compute_achieved_percentage", - store=True - ) - analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account') - # analytic_line_ids = fields.One2many('account.analytic.line', string='Analytic Account') - date_start = fields.Date(string="Start Date", required=True) - date_end = fields.Date(string="End Date", required=False) - - @api.depends("budget_amount", "achieved_amount") - def _compute_achieved_percentage(self): + string="Achieved (%)", + compute="_compute_achieved_amount", + store=True, + readonly=True, + help="Percentage of the budget achieved based on analytic lines.", + ) + analytic_account_id = fields.Many2one( + "account.analytic.account", string="Analytic Account", required=True + ) + analytic_line_ids = fields.One2many( + comodel_name="account.analytic.line", + inverse_name="budget_line_id", + string="Analytic Lines", + ) + over_budget = fields.Monetary( + string="Over Budget", + compute="_compute_achieved_amount", + store=True, + help="The amount by which the budget line exceeds its allocated budget.", + currency_field="currency_id", + ) + currency_id = fields.Many2one( + comodel_name="res.currency", + related="budget_id.currency_id", + string="Currency", + readonly=True, + ) + + @api.depends("analytic_line_ids.amount") + def _compute_achieved_amount(self): for record in self: - if record.budget_amount: - record.achieved_percentage = (record.achieved_amount / record.budget_amount) * 100 + record.achieved_amount = sum(record.analytic_line_ids.mapped("amount")) + record.achieved_percentage = ( + (record.achieved_amount / record.budget_amount) * 100 + if record.budget_amount > 0 + else 0.0 + ) + record.over_budget = max(0.0, record.achieved_amount - record.budget_amount) + + if ( + record.budget_id.on_over_budget == "warning" + and record.achieved_amount > record.budget_amount + ): + record.budget_id.warnings = "Achived amount is more than your budget!" else: - record.achieved_percentage = 0.0 + record.budget_id.warnings = False + + @api.constrains("budget_amount") + def _check_budget_amount(self): + for record in self: + if record.budget_amount < 0: + raise ValidationError("Budget amount cannot be negative.") + + + @api.model_create_multi + def create(self, vals_list): + active_budget = None + if self.env.context.get("active_id"): + active_budget = self.env["budget.budget"].browse(self.env.context.get("active_id")) + if active_budget.state != "draft": + raise UserError("Budget lines can only be created when the state is 'draft'.") + else: + for vals in vals_list: + budget_id = vals.get("budget_id") + if budget_id: + active_budget = self.env["budget.budget"].browse(budget_id) + break + + if not active_budget: + raise UserError("No budget found in context or record.") + + if active_budget.state != "draft": + raise UserError("Budget lines can only be created when the state is 'draft'.") + return super(BudgetLine, self).create(vals_list) \ No newline at end of file diff --git a/budget/views/account_analytic_view.xml b/budget/views/account_analytic_view.xml new file mode 100644 index 00000000000..bb81ea6c0bf --- /dev/null +++ b/budget/views/account_analytic_view.xml @@ -0,0 +1,15 @@ +<odoo> + <record id="budget_mangement_account_analytic_list_view" model="ir.ui.view"> + <field name="name">account.analytic.line.list</field> + <field name="model">account.analytic.line</field> + <field name="inherit_id" ref="analytic.view_account_analytic_line_tree" /> + <field name="arch" type="xml"> + <xpath expr="//list" position="attributes"> + <attribute name="editable">bottom</attribute> + </xpath> + <xpath expr="//list" position="inside"> + <field name="budget_line_id" /> + </xpath> + </field> + </record> +</odoo> \ No newline at end of file diff --git a/budget/views/budget_line_view.xml b/budget/views/budget_line_view.xml index 8e17fae3f26..6e2877eda44 100644 --- a/budget/views/budget_line_view.xml +++ b/budget/views/budget_line_view.xml @@ -8,11 +8,61 @@ sample="1" editable="bottom" > + <field name="currency_id" column_invisible="True" /> <field string="Budget" name="name" /> + <field name="budget_id" column_invisible="True" /> <field string="Analytic Account" name="analytic_account_id" /> <field string="Budget Amount" name="budget_amount" /> - <field string="Archived Amount" name="achieved_amount" /> + <field string="Achived Amount" name="achieved_amount" /> </list> </field> </record> + + <record id="view_budget_lines_graph" model="ir.ui.view"> + <field name="name">budget.lines.graph</field> + <field name="model">budget.management.budget.lines</field> + <field name="arch" type="xml"> + <graph string="Budget Line Analysis" type="bar"> + <field name="budget_id" type="row" /> + <field name="achieved_percentage" type="measure" /> + <field name="analytic_account_id" type="col" /> + </graph> + </field> + </record> + + <record id="view_budget_lines_pivot" model="ir.ui.view"> + <field name="name">budget.lines.pivot</field> + <field name="model">budget.management.budget.lines</field> + <field name="arch" type="xml"> + <pivot string="Budget Line Pivot"> + <field name="budget_id" type="row" /> + <field name="analytic_account_id" type="col" /> + <field name="budget_amount" type="measure" /> + <field name="achieved_amount" type="measure" /> + <field name="achieved_percentage" type="measure" /> + </pivot> + </field> + </record> + + <record id="view_budget_lines_gantt" model="ir.ui.view"> + <field name="name">budget.lines.gantt</field> + <field name="model">budget.management.budget.lines</field> + <field name="arch" type="xml"> + <gantt string="Budget Lines Timeline" date_start="date_start" date_stop="date_end"> + <field name="name" /> + <field name="budget_amount" sum="Total Budget" /> + </gantt> + </field> + </record> + + <record id="action_budget_lines" model="ir.actions.act_window"> + <field name="name">Budget Lines</field> + <field name="res_model">budget.management.budget.lines</field> + <field name="view_mode">graph,pivot,gantt</field> + <field name="context">{}</field> + </record> + + <menuitem id="menu_budget_lines" name="Budget Lines" + parent="budget_management_menu" action="action_budget_lines" /> + </odoo> \ No newline at end of file diff --git a/budget/views/budget_menu_view.xml b/budget/views/budget_menu_view.xml index a64f9fa5eb7..4adf66dead1 100644 --- a/budget/views/budget_menu_view.xml +++ b/budget/views/budget_menu_view.xml @@ -33,4 +33,12 @@ parent="budget_management_menu" action="action_budget_management_menu_budget" /> + + <record id="action_open_account_analytic_lines_list" model="ir.actions.act_window"> + <field name="name">Analytic Lines</field> + <field name="res_model">account.analytic.line</field> + <field name="view_mode">list</field> + <field name="context">{'default_budget_line_id': active_id}</field> + <field name="domain">[('budget_line_id', '=', context.get('default_budget_line_id'))]</field> + </record> </odoo> \ No newline at end of file diff --git a/budget/views/budget_views.xml b/budget/views/budget_views.xml index c3e454ce42b..b10dd8083e1 100644 --- a/budget/views/budget_views.xml +++ b/budget/views/budget_views.xml @@ -65,18 +65,26 @@ </field> </record> - - <record id="view_budget_management_form" model="ir.ui.view"> + <record id="view_estate_property_form" model="ir.ui.view"> <field name="name">budget.budget.form</field> <field name="model">budget.budget</field> <field name="arch" type="xml"> + <form string="Test"> + <field name="state" invisible="True" /> + <div class="alert alert-warning" role="alert" name="warnings" + invisible="not warnings"> + <field name="warnings" class="o_field_html" /> + </div> <header> <button name="onclick_reset_to_draft" type="object" string="Reset to Draft" invisible="state in ['draft']" /> + <button name="onclick_confirmed" type="object" string="Confirm" + invisible="state in ['revised','done','confirmed']" + /> <button name="onclick_revise" type="object" string="Revise" - invisible="state in ['revise','done']" + invisible="state in ['revised','done']" /> <button name="onclick_done" type="object" string="Done" invisible="state in ['done']" @@ -88,6 +96,7 @@ <h1> <field name="name" /> </h1> + <field name="currency_id" invisible="True" /> <field name="active" invisible="True" /> <group> <group> @@ -115,12 +124,22 @@ <notebook> <page name="Budget Lines"> <field name="budget_line_ids"> - <list> - <field name="name" string="Analytic Account" /> - <field name="budget_amount" string="Budget Amount" /> - <field name="achieved_amount" string="Archived Amount" /> - <field name="achieved_percentage" string="Archived (%)" + <list no_open="1" + editable="bottom" + create="state in ['draft']" + > + <field name="currency_id" column_invisible="True" /> + <field name="analytic_account_id" string="Analytic Account" + readonly="state not in ['draft']" /> + <field name="budget_amount" string="Budget Amount" + readonly="state not in ['draft']" /> + <field name="achieved_amount" string="Achived Amount" /> + <field name="achieved_percentage" string="Achived (%)" widget="progressbar" options="{'max_value': 100}" /> + <button name="%(action_open_account_analytic_lines_list)d" + type="action" + string="View" + /> </list> </field> </page> @@ -130,53 +149,4 @@ </form> </field> </record> - - <record id="view_budget_lines_graph" model="ir.ui.view"> - <field name="name">budget.lines.graph</field> - <field name="model">budget.management.budget.lines</field> - <field name="arch" type="xml"> - <graph string="Budget Line Analysis" type="bar"> - <field name="budget_id" type="row" /> - <field name="achieved_percentage" type="measure" /> - <field name="analytic_account_id" type="col" /> - </graph> - </field> - </record> - - <record id="view_budget_lines_pivot" model="ir.ui.view"> - <field name="name">budget.lines.pivot</field> - <field name="model">budget.management.budget.lines</field> - <field name="arch" type="xml"> - <pivot string="Budget Line Pivot"> - <field name="budget_id" type="row" /> - <field name="analytic_account_id" type="col" /> - <field name="budget_amount" type="measure" /> - <field name="achieved_amount" type="measure" /> - <field name="achieved_percentage" type="measure" /> - </pivot> - </field> - </record> - - <record id="view_budget_lines_gantt" model="ir.ui.view"> - <field name="name">budget.lines.gantt</field> - <field name="model">budget.management.budget.lines</field> - <field name="arch" type="xml"> - <gantt string="Budget Lines Timeline" date_start="date_start" date_stop="date_end"> - <field name="name" /> - <field name="budget_amount" sum="Total Budget" /> - </gantt> - </field> - </record> - - <record id="action_budget_lines" model="ir.actions.act_window"> - <field name="name">Budget Lines</field> - <field name="res_model">budget.management.budget.lines</field> - <field name="view_mode">graph,pivot,gantt</field> - <field name="context">{}</field> - </record> - - <menuitem id="menu_budget_lines" name="Budget Lines" - parent="budget_management_menu" action="action_budget_lines" /> - - </odoo> \ No newline at end of file diff --git a/budget/wizard/budget_wizard.py b/budget/wizard/budget_wizard.py index a8a6e8bcfc4..a7199e1f587 100644 --- a/budget/wizard/budget_wizard.py +++ b/budget/wizard/budget_wizard.py @@ -22,8 +22,6 @@ class AddBudgetWizard(models.TransientModel): analytic_account_ids = fields.Many2many( "account.analytic.account", string="Analytic Account" ) - - # analytic_account = fields.Many2many(comodel_name="account.analytic.account") def action_add_budget(self): """Creates budget records based on the selected periods.""" if self.date_start >= self.date_end: From d3564d04bca0d43f323bea301f159dc2c8f83052 Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Fri, 13 Dec 2024 18:18:32 +0530 Subject: [PATCH 15/16] [FIX] budget: Change code related to views Changed code related to graph, pivot, and Gantt chart view also added code related to that in a Python file. --- budget/models/budget_line.py | 11 +++++++---- budget/views/budget_line_view.xml | 6 +----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/budget/models/budget_line.py b/budget/models/budget_line.py index c5fb8e72f54..b90b67990e1 100644 --- a/budget/models/budget_line.py +++ b/budget/models/budget_line.py @@ -52,6 +52,10 @@ class BudgetLine(models.Model): readonly=True, ) + # Add these fields for the Gantt view + date_start = fields.Date(string="Start Date", required=True) + date_end = fields.Date(string="End Date", required=True) + @api.depends("analytic_line_ids.amount") def _compute_achieved_amount(self): for record in self: @@ -67,7 +71,7 @@ def _compute_achieved_amount(self): record.budget_id.on_over_budget == "warning" and record.achieved_amount > record.budget_amount ): - record.budget_id.warnings = "Achived amount is more than your budget!" + record.budget_id.warnings = "Achieved amount is more than your budget!" else: record.budget_id.warnings = False @@ -77,7 +81,6 @@ def _check_budget_amount(self): if record.budget_amount < 0: raise ValidationError("Budget amount cannot be negative.") - @api.model_create_multi def create(self, vals_list): active_budget = None @@ -91,11 +94,11 @@ def create(self, vals_list): if budget_id: active_budget = self.env["budget.budget"].browse(budget_id) break - + if not active_budget: raise UserError("No budget found in context or record.") if active_budget.state != "draft": raise UserError("Budget lines can only be created when the state is 'draft'.") - return super(BudgetLine, self).create(vals_list) \ No newline at end of file + return super(BudgetLine, self).create(vals_list) diff --git a/budget/views/budget_line_view.xml b/budget/views/budget_line_view.xml index 6e2877eda44..70ed5be29ee 100644 --- a/budget/views/budget_line_view.xml +++ b/budget/views/budget_line_view.xml @@ -35,11 +35,7 @@ <field name="model">budget.management.budget.lines</field> <field name="arch" type="xml"> <pivot string="Budget Line Pivot"> - <field name="budget_id" type="row" /> - <field name="analytic_account_id" type="col" /> - <field name="budget_amount" type="measure" /> - <field name="achieved_amount" type="measure" /> - <field name="achieved_percentage" type="measure" /> + <field name="analytic_account_id" type="row" /> </pivot> </field> </record> From 0d3cd8871711ca11a1e0f155c2b4a8717f7a530f Mon Sep 17 00:00:00 2001 From: Dhruvkumar Nagar <dhna@odoo.com> Date: Fri, 13 Dec 2024 18:57:46 +0530 Subject: [PATCH 16/16] [FIX] budget: removed one file removed one account analytic file. the view was not necessary. --- budget/views/account_analytic_view.xml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 budget/views/account_analytic_view.xml diff --git a/budget/views/account_analytic_view.xml b/budget/views/account_analytic_view.xml deleted file mode 100644 index bb81ea6c0bf..00000000000 --- a/budget/views/account_analytic_view.xml +++ /dev/null @@ -1,15 +0,0 @@ -<odoo> - <record id="budget_mangement_account_analytic_list_view" model="ir.ui.view"> - <field name="name">account.analytic.line.list</field> - <field name="model">account.analytic.line</field> - <field name="inherit_id" ref="analytic.view_account_analytic_line_tree" /> - <field name="arch" type="xml"> - <xpath expr="//list" position="attributes"> - <attribute name="editable">bottom</attribute> - </xpath> - <xpath expr="//list" position="inside"> - <field name="budget_line_id" /> - </xpath> - </field> - </record> -</odoo> \ No newline at end of file