diff --git a/budget_management/__init__.py b/budget_management/__init__.py new file mode 100644 index 0000000000..aee8895e7a --- /dev/null +++ b/budget_management/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/budget_management/__manifest__.py b/budget_management/__manifest__.py new file mode 100644 index 0000000000..611d9557d2 --- /dev/null +++ b/budget_management/__manifest__.py @@ -0,0 +1,23 @@ +{ + "name": "Budget Management", + "category": "Budget", + "version": "1.0", + "depends": ["base", "account", "accountant"], + "author": "djip-odoo", + "description": """ + Part of technical training + Creating Budget Management module as review task + """, + "data": [ + "security/ir.model.access.csv", + "views/budget_line_views.xml", + "views/actions_menu_and_button.xml", + "views/menu_views.xml", + "views/budget_views.xml", + "wizards/wizard_add_budgets_view.xml", + ], + "application": True, + "installable": True, + "auto_install": False, + "license": "LGPL-3", +} diff --git a/budget_management/models/__init__.py b/budget_management/models/__init__.py new file mode 100644 index 0000000000..b24e6f4110 --- /dev/null +++ b/budget_management/models/__init__.py @@ -0,0 +1,3 @@ +from . import budget +from . import budget_line +from . import account_analytic_line diff --git a/budget_management/models/account_analytic_line.py b/budget_management/models/account_analytic_line.py new file mode 100644 index 0000000000..317061db25 --- /dev/null +++ b/budget_management/models/account_analytic_line.py @@ -0,0 +1,144 @@ +from odoo import models, api +from odoo.exceptions import ValidationError + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + + @api.model_create_multi + def create(self, vals_list): + # print("* " * 100) + # print(vals_list) + # print("* " * 100) + + for vals in vals_list: + entry_date = vals.get("date") + if not entry_date: + raise ValidationError( + "The date field is required to determine the appropriate budget." + ) + + budget_line = self.env["budget.management.budget.lines"].search( + [ + ("budget_id.date_start", "<=", entry_date), + ("budget_id.date_end", ">=", entry_date), + ("budget_id.active", "=", True), + ("analytic_account_id", "=", vals.get("account_id")), + ], + limit=1, + ) + + if budget_line: + budget = budget_line.budget_id + + analytic_account_lines = self.env["account.analytic.line"].search_read( + [ + ("account_id", "=", vals.get("account_id")), + ("date", ">=", budget.date_start), + ("date", "<=", budget.date_end), + ("amount", "<", 0), + ], + fields=["amount"], + ) + # print(list(line.get("amount") for line in analytic_account_lines)) + achieved = sum(line.get("amount") for line in analytic_account_lines) + # print(budget.on_over_budget, abs(achieved + vals.get("amount")), budget_line.budget_amount) + + if budget.on_over_budget == "restriction": + if abs(achieved + vals.get("amount")) > budget_line.budget_amount: + raise ValidationError( + "You cannot create a budget line because it exceeds the allowed budget!" + ) + budget_line.achieved_amount = abs(achieved + vals.get("amount")) + budget_line.count_analytic_lines = len(analytic_account_lines) + 1 + return super(AccountAnalyticLine, self).create(vals_list) + + def write(self, vals): + if "date" in vals or "amount" in vals or "account_id" in vals: + for record in self: + entry_date = vals.get("date", record.date) + + budget_line = self.env["budget.management.budget.lines"].search( + [ + ("budget_id.date_start", "<=", entry_date), + ("budget_id.date_end", ">=", entry_date), + ("budget_id.active", "=", True), + ( + "analytic_account_id", + "=", + vals.get("account_id", record.account_id.id), + ), + ], + limit=1, + ) + + if budget_line: + budget = budget_line.budget_id + + analytic_account_lines = self.env[ + "account.analytic.line" + ].search_read( + [ + ( + "account_id", + "=", + vals.get("account_id", record.account_id.id), + ), + ("date", ">=", budget.date_start), + ("date", "<=", budget.date_end), + ("amount", "<", 0), + ("id", "!=", record.id), + ], + fields=["amount"], + ) + achieved = sum( + line.get("amount") for line in analytic_account_lines + ) + + new_amount = vals.get("amount", record.amount) + # print(budget.on_over_budget, abs(achieved - record.amount + new_amount), record.amount , new_amount, budget_line.budget_amount) + + total_achieved_amount_will_be = abs(achieved + new_amount) + + if budget.on_over_budget == "restriction": + if total_achieved_amount_will_be > budget_line.budget_amount: + raise ValidationError( + "You cannot modify the budget line because it exceeds the allowed budget!" + ) + budget_line.achieved_amount = total_achieved_amount_will_be + budget_line.count_analytic_lines = len(analytic_account_lines) + 1 + return super(AccountAnalyticLine, self).write(vals) + + def unlink(self): + for record in self: + entry_date = record.date + + budget_line = self.env["budget.management.budget.lines"].search( + [ + ("budget_id.date_start", "<=", entry_date), + ("budget_id.date_end", ">=", entry_date), + ("budget_id.active", "=", True), + ("analytic_account_id", "=", record.account_id.id), + ], + limit=1, + ) + + if budget_line: + budget = budget_line.budget_id + + # Recalculate achieved amount excluding the current record + analytic_account_lines = self.env["account.analytic.line"].search_read( + [ + ("account_id", "=", record.account_id.id), + ("date", ">=", budget.date_start), + ("date", "<=", budget.date_end), + ("amount", "<", 0), + ("id", "!=", record.id), # Exclude the current record + ], + fields=["amount"], + ) + achieved = sum(line.get("amount") for line in analytic_account_lines) + + budget_line.achieved_amount = abs(achieved) + budget_line.count_analytic_lines = len(analytic_account_lines) + return super(AccountAnalyticLine, self).unlink() diff --git a/budget_management/models/budget.py b/budget_management/models/budget.py new file mode 100644 index 0000000000..90f7b24210 --- /dev/null +++ b/budget_management/models/budget.py @@ -0,0 +1,160 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError, UserError +from markupsafe import escape, Markup + + +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, + string="Revised by" + ) + 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(compute="_check_over_budget") + 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 + end_date = record.date_end + if ( + start_date.year == end_date.year + and start_date.month == end_date.month + ): + record.name = f"Budget - {start_date.strftime('%B %Y')}" + else: + record.name = f"Budget - {start_date.strftime('%d %B, %Y')} to {end_date.strftime('%d %B, %Y')}" + 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 == "confirmed": + record.revision_id = self.env.user + record.state = "revised" + record.active = False + + new_budget = record.copy( + {"revision_id": None, "state": "draft", "active": True} + ) + + for budget_line in record.budget_line_ids: + self.env["budget.management.budget.lines"].create( + { + "budget_id": new_budget.id, + "name": budget_line.name, + "budget_amount": budget_line.budget_amount, + "achieved_amount": budget_line.achieved_amount, + "achieved_percentage": budget_line.achieved_percentage, + "analytic_account_id": budget_line.analytic_account_id.id, + "currency_id": budget_line.currency_id.id, + } + ) + + action = self.env.ref( + "budget_management.action_budget_management_menu_budget" + ) + record.message_post( + body=Markup( + f'{new_budget.name}.' + ) + ) + + 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), + ] + ) + overlapping_non_revised = False + + for budget in overlapping_budgets: + if budget.state != "revised": + overlapping_non_revised = True + break + + if overlapping_non_revised: + raise ValidationError( + "Cannot create overlapping budgets for the same period and company. If not displayed your selected period budget! please check archived budgets" + ) + + @api.depends("budget_line_ids.over_budget") + def _check_over_budget(self): + # print("* "*100) + for record in self: + # print(list(ob > 0 for ob in record.budget_line_ids.mapped("over_budget"))) + if ( + record.on_over_budget == "warning" + and any(ob > 0 for ob in record.budget_line_ids.mapped("over_budget")) + ): + record.warnings = "Achieved amount exceeds the budget!" + else: + record.warnings = False diff --git a/budget_management/models/budget_line.py b/budget_management/models/budget_line.py new file mode 100644 index 0000000000..f05ffe6f54 --- /dev/null +++ b/budget_management/models/budget_line.py @@ -0,0 +1,138 @@ +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(string="Name") + budget_id = fields.Many2one( + comodel_name="budget.budget", string="Budget", required=True, ondelete="cascade" + ) + state = fields.Selection(related="budget_id.state", readonly=True) + 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", + compute="_compute_achieved_amount", + store=True, + currency_field="currency_id", + ) + achieved_percentage = fields.Float( + string="Achieved (%)", + compute="_compute_percentage_and_over_budget", + 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 + ) + over_budget = fields.Monetary( + string="Over Budget", + compute="_compute_percentage_and_over_budget", + 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, + ) + + count_analytic_lines = fields.Integer() + start_date = fields.Date(related="budget_id.date_start") + end_date = fields.Date(related="budget_id.date_end") + user_id = fields.Many2one(related="budget_id.responsible") + + @api.depends("achieved_amount") + def _compute_percentage_and_over_budget(self): + # print("% " * 100) + for record in self: + if ( + record.analytic_account_id + and record.budget_amount + and record.achieved_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 + ) + + @api.depends("budget_amount") + def _compute_achieved_amount(self): + # print("+ " * 100) + for record in self: + if not record.analytic_account_id: + record.achieved_amount = 0.0 + record.achieved_percentage = 0.0 + record.over_budget = 0.0 + continue + analytic_account_lines = self.env["account.analytic.line"].search_read( + [ + ("auto_account_id", "=", record.analytic_account_id.id), + ("date", ">=", record.budget_id.date_start), + ("date", "<=", record.budget_id.date_end), + ("amount", "<", 0), + ], + fields=["amount"], + ) + + achieved = sum(line.get("amount") for line in analytic_account_lines) + + record.achieved_amount = abs(achieved) + + @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): + for vals in vals_list: + if vals.get("budget_id"): + active_budget = self.env["budget.budget"].browse(vals["budget_id"]) + if active_budget.state != "draft": + raise UserError( + "Budget lines can only be created when the budget is in 'draft' state." + ) + + return super(BudgetLine, self).create(vals_list) + + def open_analytic_lines_action(self): + if not self.budget_id: + raise UserError("No budget linked to this budget line.") + + budget_start_date = self.budget_id.date_start + budget_end_date = self.budget_id.date_end + + return { + "type": "ir.actions.act_window", + "name": "Analytic Lines", + "res_model": "account.analytic.line", + "view_mode": "list", + "target": "current", + "context": { + "default_account_id": self.analytic_account_id.id, + "budget_start_date": budget_start_date, + "budget_end_date": budget_end_date, + }, + "domain": [ + ("account_id", "=", self.analytic_account_id.id), + ("date", ">=", budget_start_date), + ("date", "<=", budget_end_date), + ("amount", "<", 0), + ], + } diff --git a/budget_management/security/ir.model.access.csv b/budget_management/security/ir.model.access.csv new file mode 100644 index 0000000000..5af0c73812 --- /dev/null +++ b/budget_management/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 +budget_management.access_budget_budget,access_budget_budget,budget_management.model_budget_budget,base.group_user,1,1,1,1 +budget_management.access_add_budget_wizard,access_add_budget_wizard,budget_management.model_add_budget_wizard,base.group_user,1,1,1,0 +budget_management.access_budget_management_budget_lines,access_budget_management_budget_lines,budget_management.model_budget_management_budget_lines,base.group_user,1,1,1,1 diff --git a/budget_management/views/actions_menu_and_button.xml b/budget_management/views/actions_menu_and_button.xml new file mode 100644 index 0000000000..e618e861f6 --- /dev/null +++ b/budget_management/views/actions_menu_and_button.xml @@ -0,0 +1,23 @@ + + + Budgets + budget.budget + kanban,form + +

+ No records available. +

+

+ Please check your filters or create new records. +

+
+
+ + + Budget Lines + budget.management.budget.lines + list,graph,pivot,gantt + {'default_budget_id': active_id} + [('budget_id', '=', context.get('default_budget_id'))] + +
diff --git a/budget_management/views/budget_line_views.xml b/budget_management/views/budget_line_views.xml new file mode 100644 index 0000000000..8210c50fea --- /dev/null +++ b/budget_management/views/budget_line_views.xml @@ -0,0 +1,81 @@ + + + budget.line.tree + budget.management.budget.lines + + + + + + + + + + + + + budget.management.budget.lines.pivot + budget.management.budget.lines + + + + + + + + + + budget.management.budget.lines.gantt + budget.management.budget.lines + + + + + + + +
+
+ +
analytic account:
+ + +
budget amount:
+ + +
Responsible:
+ + +
+
+
+
+
+
+ + + budget.management.budget.lines.graph + budget.management.budget.lines + + + + + + + + + +
diff --git a/budget_management/views/budget_views.xml b/budget_management/views/budget_views.xml new file mode 100644 index 0000000000..06573ae9fc --- /dev/null +++ b/budget_management/views/budget_views.xml @@ -0,0 +1,155 @@ + + + budget.budget.kanban + budget.budget + + + + +
+
+ +
+
+ +
+
+
+ +
+
+ +

+ +

+
+
+
+ + + + +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ + + budget.budget.form + budget.budget + + +
+ + +
+
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/estate/views/actions_menu_and_button.xml b/estate/views/actions_menu_and_button.xml new file mode 100644 index 0000000000..89859281c7 --- /dev/null +++ b/estate/views/actions_menu_and_button.xml @@ -0,0 +1,39 @@ + + + + Real Estate Properties + estate.property + kanban,list,form + {'search_default_available': True} + +

+ No records available. +

+

+ Please check your filters or create new records. +

+
+
+ + + + Property Types + estate.property.types + list,form + + + + + Property Tags + estate.property.tags + list + + + + + Offers + estate.property.offers + [('property_type_id', '=', active_id)] + list,form + +
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 0000000000..70b3806d5e --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,40 @@ + + + + estate.property.offer.list + estate.property.offers + + + + + + + + +

+ +

+ + + + + + + + + + + +
+ +
+
+ + + + estate.property.types.search + estate.property.types + search + + + + + + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 0000000000..1daa921d41 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,220 @@ + + + + estate.property.list + estate.property + + +
+
+ + + + + + + + + + + + + + + + +
+
+
+ + + + estate.property.form + estate.property + +
+
+
+ +

+ +
+ +
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + + estate.property.search + estate.property + search + + + + + + + + + + + + + + + + + + + + + + + estate.property.kanban + estate.property + + + + + +
+
+

+ +

+ Expected price: + + +
Best + price: +
+
+
+ Selling price: + +
+ +
+ +
+
+
+
+
+
+
diff --git a/estate/views/menu_views.xml b/estate/views/menu_views.xml new file mode 100644 index 0000000000..486fbd2192 --- /dev/null +++ b/estate/views/menu_views.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 0000000000..f83370416f --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,17 @@ + + + res.users.form. + res.users + + + + Partner Name + + + + + + + + + diff --git a/estate/views/website_contact_form_template.xml b/estate/views/website_contact_form_template.xml new file mode 100644 index 0000000000..d2329d41b8 --- /dev/null +++ b/estate/views/website_contact_form_template.xml @@ -0,0 +1,45 @@ + + + + + diff --git a/estate/views/website_menu.xml b/estate/views/website_menu.xml new file mode 100644 index 0000000000..987bf972ea --- /dev/null +++ b/estate/views/website_menu.xml @@ -0,0 +1,7 @@ + + + Properties + /active_properties + + + diff --git a/estate/views/website_properties_templates.xml b/estate/views/website_properties_templates.xml new file mode 100644 index 0000000000..e2df59b72e --- /dev/null +++ b/estate/views/website_properties_templates.xml @@ -0,0 +1,63 @@ + + + \ No newline at end of file diff --git a/estate/views/website_property_page_template.xml b/estate/views/website_property_page_template.xml new file mode 100644 index 0000000000..4b04a45462 --- /dev/null +++ b/estate/views/website_property_page_template.xml @@ -0,0 +1,34 @@ + + + diff --git a/estate/wizards/__init__.py b/estate/wizards/__init__.py new file mode 100644 index 0000000000..e9c285b027 --- /dev/null +++ b/estate/wizards/__init__.py @@ -0,0 +1 @@ +from . import estate_property_offers_wizard \ No newline at end of file diff --git a/estate/wizards/estate_property_offers_wizard.py b/estate/wizards/estate_property_offers_wizard.py new file mode 100644 index 0000000000..e40e4e085d --- /dev/null +++ b/estate/wizards/estate_property_offers_wizard.py @@ -0,0 +1,30 @@ +from odoo import models, fields + + +class PropertyOfferWizard(models.TransientModel): + _name = "estate.property.offers.wizard" + _description = "wizard to add multiple offers" + + price = fields.Float(required=True) + partner_id = fields.Many2one("res.partner") + validity = fields.Integer(default=7) + + def action_add_offers(self): + property_ids = self.env.context.get("active_ids", []) + + offer_vals_list = [] + for property_id in property_ids: + offer_vals = { + "property_id": property_id, + "price": self.price, + "partner_id": self.partner_id.id, + "validity": self.validity, + } + offer_vals_list.append(offer_vals) + # print(offer_vals_list) + # print("**" * 100) + + if len(offer_vals_list) > 0: + self.env["estate.property.offers"].create(offer_vals_list) + + return {"type": "ir.actions.act_window_close"} diff --git a/estate/wizards/wizard_add_offers.xml b/estate/wizards/wizard_add_offers.xml new file mode 100644 index 0000000000..fbe460e6d1 --- /dev/null +++ b/estate/wizards/wizard_add_offers.xml @@ -0,0 +1,34 @@ + + + + + add.offer.wizard.form + estate.property.offers.wizard + +
+ + + + + + + +
+
+
+
+
+ + + + Add Offer + estate.property.offers.wizard + form + + new + +
+
diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 0000000000..05e9cf89d0 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,21 @@ +{ + "name": "Estate Account", + "version": "1.0", + "depends": ["base", "estate", "account"], + "author": "djip-odoo", + "description": """ + part of technical training + """, + "data": [ + "report/estate_estate_property_templates.xml", + "security/ir.model.access.csv", + "views/actions_smart_button.xml", + "views/estate_property_views.xml", + ], + "demo": [ + "demo/demo_invoice_data_property.xml", + ], + "installable": True, + "auto_install": True, + "license": "LGPL-3", +} diff --git a/estate_account/demo/demo_invoice_data_property.xml b/estate_account/demo/demo_invoice_data_property.xml new file mode 100644 index 0000000000..b3012ccb4a --- /dev/null +++ b/estate_account/demo/demo_invoice_data_property.xml @@ -0,0 +1,29 @@ + + + + out_invoice + + + + + + + + out_invoice + + + + + + + diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 0000000000..e6773ef3d0 --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1,2 @@ +from . import estate_property +from . import account_move diff --git a/estate_account/models/account_move.py b/estate_account/models/account_move.py new file mode 100644 index 0000000000..6ef41613ac --- /dev/null +++ b/estate_account/models/account_move.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + property_id = fields.Many2one("estate.property", string="Property") diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 0000000000..5792512f43 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,44 @@ +from odoo import models, Command, api, fields + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + invoice_ids = fields.One2many("account.move", "property_id", string="Invoices") + + invoice_count = fields.Integer(compute="_calc_invoices", string="Invoice Count") + + @api.depends("invoice_ids") + def _calc_invoices(self): + for record in self: + record.invoice_count = len(record.invoice_ids) + + def onclick_sold(self): + super().onclick_sold() + # print(" reached ".center(100, '=')) + self.check_access('write') + invoice = self.env["account.move"].sudo().create( + { + "partner_id": self.partner_id.id, + "move_type": "out_invoice", + "property_id": self.id, + "invoice_line_ids": [ + Command.create( + { + "name": "6% of the selling price", + "quantity": 1, + "price_unit": self.selling_price * 0.06, + } + ), + Command.create( + { + "name": "Administrative price", + "quantity": 1, + "price_unit": 100, + } + ), + ], + } + ) + + return invoice diff --git a/estate_account/report/estate_estate_property_templates.xml b/estate_account/report/estate_estate_property_templates.xml new file mode 100644 index 0000000000..21e532f1ce --- /dev/null +++ b/estate_account/report/estate_estate_property_templates.xml @@ -0,0 +1,12 @@ + + + diff --git a/estate_account/security/ir.model.access.csv b/estate_account/security/ir.model.access.csv new file mode 100644 index 0000000000..4ea2838ed4 --- /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 +estate.manager_access_estate_property_invoice,manager_access_estate_property_name_invoice,account.model_account_move,estate.group_estate_user_manager,0,0,0,0 +estate.agent_access_estate_property_invoice,agent_access_estate_property_name_invoice,account.model_account_move,estate.group_estate_user_agent,0,0,0,0 diff --git a/estate_account/views/actions_smart_button.xml b/estate_account/views/actions_smart_button.xml new file mode 100644 index 0000000000..352fc265de --- /dev/null +++ b/estate_account/views/actions_smart_button.xml @@ -0,0 +1,8 @@ + + + Invoices + account.move + [('property_id', '=', active_id)] + list,form + + diff --git a/estate_account/views/estate_property_views.xml b/estate_account/views/estate_property_views.xml new file mode 100644 index 0000000000..7bd74fb6f4 --- /dev/null +++ b/estate_account/views/estate_property_views.xml @@ -0,0 +1,30 @@ + + + estate.property.form + estate.property + + + +
+ +
+
+
+
+
diff --git a/warranty_configuration/__init__.py b/warranty_configuration/__init__.py new file mode 100644 index 0000000000..aee8895e7a --- /dev/null +++ b/warranty_configuration/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/warranty_configuration/__manifest__.py b/warranty_configuration/__manifest__.py new file mode 100644 index 0000000000..d5dc0c54f5 --- /dev/null +++ b/warranty_configuration/__manifest__.py @@ -0,0 +1,25 @@ +{ + "name": "Warranty Configuration", + "version": "1.0", + "depends": ["base", "sale_management", "spreadsheet"], + "author": "djip-odoo", + "description": """ + Part of technical training + Creating Warranty Configuration module for assignment 1 + """, + "data": [ + "security/ir.model.access.csv", + "wizards/add_warranty_wizard_view.xml", + "views/sales_management_menu.xml", + "views/warranty_configuration_views.xml", + "views/product_views.xml", + "views/sale_order_views.xml", + ], + 'demo':[ + 'demo/product_demo.xml', + 'demo/warranty_configuration_demo.xml', + ], + "installable": True, + "application": False, + "license": "LGPL-3", +} diff --git a/warranty_configuration/demo/product_demo.xml b/warranty_configuration/demo/product_demo.xml new file mode 100644 index 0000000000..9a525df9a9 --- /dev/null +++ b/warranty_configuration/demo/product_demo.xml @@ -0,0 +1,29 @@ + + + + Basic Warranty Service + service + WARRANTY_BASIC + 50.0 + 30.0 + + + + + Premium Warranty Service + service + WARRANTY_PREMIUM + 100.0 + 70.0 + + + + + Extended Warranty Service + service + WARRANTY_EXTENDED + 150.0 + 100.0 + + + diff --git a/warranty_configuration/demo/warranty_configuration_demo.xml b/warranty_configuration/demo/warranty_configuration_demo.xml new file mode 100644 index 0000000000..2515bcffc3 --- /dev/null +++ b/warranty_configuration/demo/warranty_configuration_demo.xml @@ -0,0 +1,23 @@ + + + + Warranty - 1 year + + 1 + 10.0 + + + + Warranty - 2 year + + 2 + 15.0 + + + + Warranty - 3 year + + 3 + 20.0 + + diff --git a/warranty_configuration/models/__init__.py b/warranty_configuration/models/__init__.py new file mode 100644 index 0000000000..7ce3493c7b --- /dev/null +++ b/warranty_configuration/models/__init__.py @@ -0,0 +1,3 @@ +from . import warranty_configuration +from . import product_template +from . import sale_order_line diff --git a/warranty_configuration/models/product_template.py b/warranty_configuration/models/product_template.py new file mode 100644 index 0000000000..b610764b83 --- /dev/null +++ b/warranty_configuration/models/product_template.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + warranty = fields.Boolean(string="Is Warranty Available ?", default=False) diff --git a/warranty_configuration/models/sale_order_line.py b/warranty_configuration/models/sale_order_line.py new file mode 100644 index 0000000000..07234eebc2 --- /dev/null +++ b/warranty_configuration/models/sale_order_line.py @@ -0,0 +1,18 @@ +from odoo import models, fields, api + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + parent_sale_order_line_id = fields.Many2one( + comodel_name="sale.order.line", + readonly=True, + ondelete="cascade", + string="parent sale order line id", + ) + + def action_remove_all_products(self): + # tmp=[] + # for i in (self.order_id.order_line): + # tmp.append(i.product_template_id.name) + self.order_id.write({"order_line": [(6, 0, False)]}) diff --git a/warranty_configuration/models/warranty_configuration.py b/warranty_configuration/models/warranty_configuration.py new file mode 100644 index 0000000000..56311ca614 --- /dev/null +++ b/warranty_configuration/models/warranty_configuration.py @@ -0,0 +1,14 @@ +from odoo import fields, models + + +class WarrantyConfiguration(models.Model): + _name = "warranty.configuration" + _description = "warranty order line for selected products !" + + name = fields.Char( + required=True, + string="Name", + ) + product_id = fields.Many2one("product.product", string="Product", index=True) + year = fields.Integer(default=0, required=True) + percentage = fields.Float(default=0.0, digits=(5, 2), required=True) diff --git a/warranty_configuration/security/ir.model.access.csv b/warranty_configuration/security/ir.model.access.csv new file mode 100644 index 0000000000..866c8e7d3b --- /dev/null +++ b/warranty_configuration/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 +warranty_configuration.access_warranty_configuration,access_warranty_configuration,warranty_configuration.model_warranty_configuration,base.group_user,1,1,1,1 +warranty_configuration.access_add_warranty_wizard,access_add_warranty_wizard,warranty_configuration.model_add_warranty_wizard,base.group_user,1,1,1,1 +warranty_configuration.access_add_warranty_line_wizard,access_add_warranty_line_wizard,warranty_configuration.model_add_warranty_line_wizard,base.group_user,1,1,1,1 diff --git a/warranty_configuration/views/product_views.xml b/warranty_configuration/views/product_views.xml new file mode 100644 index 0000000000..0ba7de492f --- /dev/null +++ b/warranty_configuration/views/product_views.xml @@ -0,0 +1,14 @@ + + + product.template.form.warranty.configuration + product.template + + + + + + + + + + diff --git a/warranty_configuration/views/sale_order_views.xml b/warranty_configuration/views/sale_order_views.xml new file mode 100644 index 0000000000..9dfce03548 --- /dev/null +++ b/warranty_configuration/views/sale_order_views.xml @@ -0,0 +1,23 @@ + + + sale.order.form.add.warranty + sale.order + + + +