diff --git a/dental/__init__.py b/dental/__init__.py new file mode 100644 index 0000000000..f7209b1710 --- /dev/null +++ b/dental/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers diff --git a/dental/__manifest__.py b/dental/__manifest__.py new file mode 100644 index 0000000000..7d18be0a63 --- /dev/null +++ b/dental/__manifest__.py @@ -0,0 +1,26 @@ +{ + "name": "dental", + "version": "0.1", + "depends": ["base", "mail", "account", "website", "portal"], + "description": "Dental Application", + "data": [ + "security/ir.model.access.csv", + "data/dental_patients.xml", + "views/dental_controller.xml", + "report/dental_patient_template.xml", + "report/dental_patient_report.xml", + "views/tag_views.xml", + "views/history_views.xml", + "views/medication_views.xml", + "views/habits_views.xml", + "views/allergies_views.xml", + "views/chronic_condition_views.xml", + "views/medical_aids_views.xml", + "views/patient_views.xml", + "views/dental_portal_template.xml", + "views/menuitem.xml", + ], + "installable": True, + "application": True, + "license": "LGPL-3", +} diff --git a/dental/controllers/__init__.py b/dental/controllers/__init__.py new file mode 100644 index 0000000000..8c3feb6f56 --- /dev/null +++ b/dental/controllers/__init__.py @@ -0,0 +1 @@ +from . import portal diff --git a/dental/controllers/portal.py b/dental/controllers/portal.py new file mode 100644 index 0000000000..f49a0936f3 --- /dev/null +++ b/dental/controllers/portal.py @@ -0,0 +1,119 @@ +import math +from odoo.http import Controller, route, request + + +class PortalDental(Controller): + + @route( + ["/my/dental", "/my/dental/page/"], + type="http", + auth="public", + website=True, + ) + def portal_my_dental(self, page=1, per_page=6, **kw): + page = int(page) + per_page = int(per_page) + offset = (page - 1) * per_page + limit = per_page + current_user = request.env.user + domain = [("guaranator", "=", current_user.id)] + patients = ( + request.env["dental.patients"] + .sudo() + .search(domain, limit=limit, offset=offset) + ) + total_patinets = patients.search_count(domain) + total_pages = math.ceil(total_patinets / per_page) + pager = { + "url": "/my/dental/page/" + str(page), + "total": total_patinets, + "current_page": page, + "total_page": total_pages, + "step": per_page, + } + values = {"patients": patients, "pager": pager} + return request.render("dental.dental_patient_template", values) + + @route( + "/my/dental//", + type="http", + auth="public", + website=True, + ) + def portal_dental_patient(self, patient_id, **kw): + patients = request.env["dental.patients"].sudo().browse(patient_id) + values = {"patient": patients} + return request.render("dental.dental_individual_patient_template", values) + + @route( + "/my/dental///", + type="http", + auth="public", + website=True, + ) + def portal_dental_patient_data( + self, patient_id, data, sortby=None, filterby=None, **kw + ): + patient = request.env["dental.patients"].sudo().browse(patient_id) + if not patient.exists(): + return request.render("website.page_404", {}) + elif data == "dentalhistory": + domain = [("patient", "=", patient.id)] + if filterby is None: + filterby = "all" + # pass the filter as tuple in search domain + patient_history = ( + request.env["dental.history"].sudo().search(domain, order=sortby) + ) + values = { + "patient": patient, + "patient_history": patient_history, + "sort_by": sortby, + "filter_by": filterby, + } + return request.render("dental.dental_patient_history_template", values) + elif data == "personal": + values = {"patient": self.patient_data(patient_id)} + return request.render("dental.dental_patient_personal_template", values) + elif data == "medical_history": + values = {"history": self.patient_medical_history(patient_id)} + return request.render( + "dental.dental_patient_medical_history_template", values + ) + elif data == "medical_aid": + values = { + "medical_aid": self.patient_medical_aid(patient_id), + "patient": patient, + } + return request.render("dental.dental_patient_medical_aid_template", values) + + return request.render("website.page_404", {}) + + def patient_data(self, patient_id): + return request.env["dental.patients"].sudo().browse(patient_id) + + def patient_medical_history(self, patient_id): + history = ( + request.env["dental.history"] + .sudo() + .search([("patient", "=", patient_id)], order="date") + ) + return history[len(history) - 1] + + def patient_medical_aid(self, patient_id): + patient = request.env["dental.patients"].sudo().browse(patient_id) + return request.env["dental.medical.aid"].sudo().browse(patient.medical_aid.id) + + @route( + "/my/dental///medical_history/", + type="http", + auth="public", + website=True, + ) + def medical_history_individual(self, name, patient_id, history_id, **kw): + history = request.env["dental.history"].sudo().browse(history_id) + patient = request.env["dental.patients"].sudo().browse(patient_id) + values = {"history": history, "patient": patient} + return request.render( + "dental.dental_patient_medical_history_clicked_template", values + ) diff --git a/dental/data/dental_patients.xml b/dental/data/dental_patients.xml new file mode 100644 index 0000000000..9ff9f6ded3 --- /dev/null +++ b/dental/data/dental_patients.xml @@ -0,0 +1,218 @@ + + + + + Life Insurence + new + +91 123456789 + xyz@email.com + + + + Emergency Kit + new + +91 123456789 + xyz@email.com + + + + Medical Help + progress + +91 123456789 + xyz@email.com + + + + Medicine + done + +91 123456789 + xyz@email.com + + + + + + Ram + new + Not Available + Normal + single + + + + + + Shyam + new + Not Available + Normal + single + + + + + Krishna + new + Not Available + Normal + single + + + + Shiva + new + Not Available + Normal + single + + + + Vishnu + done + Not Available + Normal + single + + + + + Brahma + invoice + Not Available + Normal + single + + + + + Laxmi + done + Not Available + Normal + single + + + + + Sita + done + Not Available + Normal + single + + + + + Radha + done + Not Available + Normal + single + + + + + Parvati + today + Not Available + Normal + single + + + + + Arjun + new + Not Available + Normal + single + + + + + Hanuman + today + Not Available + Normal + single + + + + + Diabetes Mellitus + A chronic condition that affects how the body processes blood sugar (glucose) + + + + Hypertension + A condition in which the blood pressure in the arteries is persistently elevated. + + + Chronic Obstructive Pulmonary Disease (COPD) + A group of progressive lung diseases that block airflow and make it difficult to breathe. + + + Asthma + A condition in which a person's airways become inflamed, narrow, and swell, making breathing difficult. + + + Rheumatoid Arthritis + A chronic inflammatory disorder affecting the joints, including those in the hands and feet. + + + + + + \ No newline at end of file diff --git a/dental/models/__init__.py b/dental/models/__init__.py new file mode 100644 index 0000000000..111f4ab481 --- /dev/null +++ b/dental/models/__init__.py @@ -0,0 +1,8 @@ +from . import dental_patients +from . import dental_medical_aids +from . import dental_chronic_condition +from . import dental_allergies +from . import dental_habits +from . import dental_medication +from . import dental_history +from . import dental_tags diff --git a/dental/models/dental_allergies.py b/dental/models/dental_allergies.py new file mode 100644 index 0000000000..497cb283ae --- /dev/null +++ b/dental/models/dental_allergies.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class Allergies(models.Model): + _name = "dental.allergies" + _description = "Allergies Records" + _inherit = ["mail.thread", "mail.activity.mixin"] + _order = "sequence ASC" + name = fields.Char(string="Condition Name", required=True) + description = fields.Text(string="Description") + sequence = fields.Integer() diff --git a/dental/models/dental_chronic_condition.py b/dental/models/dental_chronic_condition.py new file mode 100644 index 0000000000..a62844bef9 --- /dev/null +++ b/dental/models/dental_chronic_condition.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class ChronicCondition(models.Model): + _name = "dental.chronic.condition" + _description = "Chronic Dental Conditions" + _inherit = ["mail.thread", "mail.activity.mixin"] + _order = "sequence ASC" + name = fields.Char(string="Condition Name", required=True) + description = fields.Text(string="Description") + sequence = fields.Integer() diff --git a/dental/models/dental_habits.py b/dental/models/dental_habits.py new file mode 100644 index 0000000000..f9a630f8b1 --- /dev/null +++ b/dental/models/dental_habits.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class HabitsAbuse(models.Model): + _name = "dental.habits.abuse" + _description = "Habits and Abuse Records" + _inherit = ["mail.thread", "mail.activity.mixin"] + _order = 'sequence DESC' + name = fields.Char(string="Condition Name", required=True) + description = fields.Text(string="Description") + sequence = fields.Integer() diff --git a/dental/models/dental_history.py b/dental/models/dental_history.py new file mode 100644 index 0000000000..825dbf67fe --- /dev/null +++ b/dental/models/dental_history.py @@ -0,0 +1,79 @@ +import datetime +from odoo import fields, models + + +class History(models.Model): + _name = "dental.history" + _description = "History of patients" + _inherit = ["mail.thread", "mail.activity.mixin"] + + name = fields.Char() + tags = fields.Many2many("dental.tags") + patient = fields.Many2one("dental.patients") + date = fields.Date(default=datetime.datetime.now()) + did_not_attend = fields.Boolean(required=True) + responsible = fields.Char() + company_id = fields.Many2one("res.company") + main_complaint = fields.Text() + history = fields.Text() + xfiel1 = fields.Image(string="X-ray file 1") + xfiel2 = fields.Image(string="X-ray file 2") + habits = fields.Text() + oral_observation = fields.Text(string="Extra-Oral observation") + aligner_file1 = fields.Binary(string="Clear Aligner File 1") + aligner_file2 = fields.Binary(string="Clear Aligner File 2") + treatment_notes = fields.Text() + consultation_type = fields.Selection( + copy=False, + selection=[ + ("bite_wing", "Bite-wings"), + ("scan", "Scan"), + ("consultation", "Consultation"), + ("no_consultation", "No Consultation"), + ], + ) + call_out = fields.Boolean() + scale_polish = fields.Boolean(string="Scale and polish") + flouride = fields.Boolean(string="Flouride") + filling_description = fields.Text() + aligner_delivery_place = fields.Boolean( + string="Aligner delivery and attachmentplaced" + ) + whitening = fields.Boolean() + fissure_sealant_quantity = fields.Float() + attachments_removed = fields.Boolean() + aligner_follow = fields.Boolean(string="Aligner Follow-up Scan") + other = fields.Text() + notes = fields.Text() + tooth_18_staining = fields.Boolean(string="18 Staining") + tooth_17_staining = fields.Boolean(string="17 Staining") + tooth_16_staining = fields.Boolean(string="16 Staining") + tooth_15_staining = fields.Boolean(string="15 Staining") + tooth_14_staining = fields.Boolean(string="14 Staining") + tooth_13_staining = fields.Boolean(string="13 Staining") + tooth_12_staining = fields.Boolean(string="12 Staining") + tooth_11_staining = fields.Boolean(string="11 Staining") + tooth_28_staining = fields.Boolean(string="28 Staining") + tooth_27_staining = fields.Boolean(string="27 Staining") + tooth_26_staining = fields.Boolean(string="26 Staining") + tooth_25_staining = fields.Boolean(string="25 Staining") + tooth_24_staining = fields.Boolean(string="24 Staining") + tooth_23_staining = fields.Boolean(string="23 Staining") + tooth_22_staining = fields.Boolean(string="22 Staining") + tooth_21_staining = fields.Boolean(string="21 Staining") + tooth_38_staining = fields.Boolean(string="38 Staining") + tooth_37_staining = fields.Boolean(string="37 Staining") + tooth_36_staining = fields.Boolean(string="36 Staining") + tooth_35_staining = fields.Boolean(string="35 Staining") + tooth_34_staining = fields.Boolean(string="34 Staining") + tooth_33_staining = fields.Boolean(string="33 Staining") + tooth_32_staining = fields.Boolean(string="32 Staining") + tooth_31_staining = fields.Boolean(string="31 Staining") + tooth_41_staining = fields.Boolean(string="41 Staining") + tooth_42_staining = fields.Boolean(string="42 Staining") + tooth_43_staining = fields.Boolean(string="43 Staining") + tooth_44_staining = fields.Boolean(string="44 Staining") + tooth_45_staining = fields.Boolean(string="45 Staining") + tooth_46_staining = fields.Boolean(string="46 Staining") + tooth_47_staining = fields.Boolean(string="47 Staining") + tooth_48_staining = fields.Boolean(string="48 Staining") diff --git a/dental/models/dental_medical_aids.py b/dental/models/dental_medical_aids.py new file mode 100644 index 0000000000..051495a03f --- /dev/null +++ b/dental/models/dental_medical_aids.py @@ -0,0 +1,27 @@ +from odoo import fields, models + + +class MedicalAids(models.Model): + + _name = "dental.medical.aid" + _description = "Medical Aid Records" + _inherit = ["mail.thread", "mail.activity.mixin"] + _order = 'sequence DESC' + name = fields.Char("Name") + image = fields.Image("Medical Aid") + contact = fields.Char("Contact") + phone = fields.Char("Phone") + email = fields.Char("Email") + company_id = fields.Many2one("res.company") + notes = fields.Char("Notes") + state = fields.Selection( + required=True, + copy=False, + default="new", + selection=[ + ("new", "New"), + ("progress", "In Progress"), + ("done", "Done"), + ], + ) + sequence = fields.Integer() diff --git a/dental/models/dental_medication.py b/dental/models/dental_medication.py new file mode 100644 index 0000000000..7a6b4e8af9 --- /dev/null +++ b/dental/models/dental_medication.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class Medication(models.Model): + _name = "dental.medication" + _description = "Medication" + _inherit = ["mail.thread", "mail.activity.mixin"] + _order = 'sequence DESC' + name = fields.Char("Name", required=True) + patients_id = fields.Many2one("dental.patients", string="Patient") + sequence = fields.Integer() diff --git a/dental/models/dental_patients.py b/dental/models/dental_patients.py new file mode 100644 index 0000000000..8870999e15 --- /dev/null +++ b/dental/models/dental_patients.py @@ -0,0 +1,110 @@ +from datetime import date +import logging +from odoo import Command, fields, models + + +class Patients(models.Model): + _name = "dental.patients" + _description = "Detail Patients records" + _inherit = ["mail.thread", "mail.activity.mixin"] + name = fields.Char("Name", required=True) + patient_image = fields.Image("Patient Image") + hospitalised = fields.Char("Hospitalised this year") + under_specialist_care = fields.Char() + psychiatric_history = fields.Char() + chronic_condition = fields.Many2many("dental.chronic.condition") + medication = fields.Many2many("dental.medication") + habits = fields.Many2many("dental.habits.abuse", string="Haits/Substance Abuse") + allergy_ids = fields.Many2many("dental.allergies", string="Allergies") + state = fields.Selection( + required=True, + copy=False, + default="new", + selection=[ + ("new", "New"), + ("today", "To do today"), + ("done", "Done"), + ("invoice", "To invoice"), + ], + tracking=True, + ) + female = fields.Boolean(string="FEMALE") + pregnant = fields.Boolean(string="Are you pregnant") + nursing = fields.Boolean(string="Are you nursing") + neither = fields.Selection( + copy=False, + default="neither", + selection=[ + ("neither", "Neither"), + ("hormone", "Hormone Replacement Treatment"), + ("female", "Birth Control"), + ], + string="Are you on...", + ) + medical_aid = fields.Many2one("dental.medical.aid") + medical_aid_plan = fields.Char() + medical_aid_number = fields.Char() + main_member_code = fields.Integer() + dependent_code = fields.Char() + ocupation_grade = fields.Char(string="Ocupation or Grade") + identity_no = fields.Char(string="Identity number") + birth_data = fields.Date(strong="Date of Birth") + gender = fields.Selection( + selection=[ + ("male", "Male"), + ("female", "Female"), + ("transgender", "Transgender"), + ], + ) + marital_status = fields.Selection( + selection=[ + ("single", "Single"), + ("married", "Married"), + ("divorsed", "Divorsed"), + ("widowed", "Widowed"), + ] + ) + note = fields.Text() + gp_name = fields.Many2one("res.users", string="GP's Name") + history_ids = fields.One2many("dental.history", "patient") + emergency_contact = fields.Many2one("res.users") + mobile = fields.Char(related="emergency_contact.partner_id.phone") + company_id = fields.Many2one("res.company") + signature = fields.Image("Consent Signature", help="Signature", copy=False) + guaranator = fields.Many2one("res.users") + guaranator_mobile = fields.Char( + related="guaranator.partner_id.mobile", string="Guaranator Mobile Phone" + ) + guaranator_phone = fields.Char( + related="guaranator.partner_id.phone", string="Phone" + ) + guaranator_email = fields.Char( + related="guaranator.partner_id.email", string="Email" + ) + tag_ids = fields.Many2many("dental.tags", string="Tags") + guaranter_company = fields.Many2one("res.company", string="Company") + + def generate_invoice_from_patient(self): + logging.info("Generating invoice...") + if self.state == "done": + for patient in self: + invoice_obj = self.env["account.move"] + invoice_vals = { + "partner_id": patient.guaranator.partner_id.id, + "move_type": "out_invoice", + "invoice_date": date.today(), + "state": "draft", + "ref": patient.name, + "invoice_line_ids": [ + Command.create( + { + "name": patient.name, + "quantity": 1, + "price_unit": 1000 * 0.06, + "invoice_date": date.today(), + } + ) + ], + } + invoice_obj.create(invoice_vals) + self.state = "invoice" diff --git a/dental/models/dental_tags.py b/dental/models/dental_tags.py new file mode 100644 index 0000000000..c2fd5bce04 --- /dev/null +++ b/dental/models/dental_tags.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class Tags(models.Model): + _name = "dental.tags" + _description = "Tags for patients" + name = fields.Char("Tag", required=True) diff --git a/dental/report/dental_patient_report.xml b/dental/report/dental_patient_report.xml new file mode 100644 index 0000000000..80777a2b4f --- /dev/null +++ b/dental/report/dental_patient_report.xml @@ -0,0 +1,12 @@ + + + Dental Report + dental.patients + qweb-pdf + dental.dental_invoice_report_template + dental.dental_invoice_report_template + 'Dental Report- %s' % (object.name or 'Attendee').replace('/','') + + report + + \ No newline at end of file diff --git a/dental/report/dental_patient_template.xml b/dental/report/dental_patient_template.xml new file mode 100644 index 0000000000..47848877a6 --- /dev/null +++ b/dental/report/dental_patient_template.xml @@ -0,0 +1,35 @@ + + + \ No newline at end of file diff --git a/dental/security/ir.model.access.csv b/dental/security/ir.model.access.csv new file mode 100644 index 0000000000..29a7dbd635 --- /dev/null +++ b/dental/security/ir.model.access.csv @@ -0,0 +1,9 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +dental.access_dental_patients,access_dental_patients,dental.model_dental_patients,base.group_user,1,1,1,1 +dental.access_dental_medical_aid,access_dental_medical_aid,dental.model_dental_medical_aid,base.group_user,1,1,1,1 +dental.access_dental_chronic_condition,access_dental_chronic_condition,dental.model_dental_chronic_condition,base.group_user,1,1,1,1 +dental.access_dental_allergies,access_dental_allergies,dental.model_dental_allergies,base.group_user,1,1,1,1 +dental.access_dental_habits_abuse,access_dental_habits_abuse,dental.model_dental_habits_abuse,base.group_user,1,1,1,1 +dental.access_dental_medication,access_dental_medication,dental.model_dental_medication,base.group_user,1,1,1,1 +dental.access_dental_history,access_dental_history,dental.model_dental_history,base.group_user,1,1,1,1 +dental.access_dental_tags,access_dental_tags,dental.model_dental_tags,base.group_user,1,1,1,1 diff --git a/dental/static/src/img/Bill.svg b/dental/static/src/img/Bill.svg new file mode 100644 index 0000000000..51d1968db6 --- /dev/null +++ b/dental/static/src/img/Bill.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/dental/static/src/img/bag.svg b/dental/static/src/img/bag.svg new file mode 100644 index 0000000000..148d08a226 --- /dev/null +++ b/dental/static/src/img/bag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dental/static/src/img/dental_icon.png b/dental/static/src/img/dental_icon.png new file mode 100644 index 0000000000..672fa9071a Binary files /dev/null and b/dental/static/src/img/dental_icon.png differ diff --git a/dental/static/src/img/folder.svg b/dental/static/src/img/folder.svg new file mode 100644 index 0000000000..e25122ed53 --- /dev/null +++ b/dental/static/src/img/folder.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/dental/static/src/img/icon.svg b/dental/static/src/img/icon.svg new file mode 100644 index 0000000000..12e8ec084c --- /dev/null +++ b/dental/static/src/img/icon.svg @@ -0,0 +1 @@ + diff --git a/dental/static/src/img/tasks.svg b/dental/static/src/img/tasks.svg new file mode 100644 index 0000000000..d43aeaacdf --- /dev/null +++ b/dental/static/src/img/tasks.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/dental/views/allergies_views.xml b/dental/views/allergies_views.xml new file mode 100644 index 0000000000..1bbec1ba57 --- /dev/null +++ b/dental/views/allergies_views.xml @@ -0,0 +1,53 @@ + + + Allergies + dental.allergies + tree,form + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + + Allergies Tree + dental.allergies + + + + + + + + + + + + Allergies + dental.allergies + +
+ +
+

+ +

+ + + +
+
+ +
+ + + +
+
+
+
+ + +
\ No newline at end of file diff --git a/dental/views/chronic_condition_views.xml b/dental/views/chronic_condition_views.xml new file mode 100644 index 0000000000..4aaab5446c --- /dev/null +++ b/dental/views/chronic_condition_views.xml @@ -0,0 +1,49 @@ + + + Chronic Conditions + dental.chronic.condition + tree,form + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + Chronic Conditions Tree + dental.chronic.condition + + + + + + + + + + + Chronic Conditions + dental.chronic.condition + +
+ +
+

+ +

+ + + +
+
+ +
+ + + +
+
+
+
+
\ No newline at end of file diff --git a/dental/views/dental_controller.xml b/dental/views/dental_controller.xml new file mode 100644 index 0000000000..7c4db40645 --- /dev/null +++ b/dental/views/dental_controller.xml @@ -0,0 +1,16 @@ + + + diff --git a/dental/views/dental_portal_template.xml b/dental/views/dental_portal_template.xml new file mode 100644 index 0000000000..2c69981d97 --- /dev/null +++ b/dental/views/dental_portal_template.xml @@ -0,0 +1,510 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dental/views/habits_views.xml b/dental/views/habits_views.xml new file mode 100644 index 0000000000..4e4d92cd26 --- /dev/null +++ b/dental/views/habits_views.xml @@ -0,0 +1,49 @@ + + + Habits/Substance Abuse + dental.habits.abuse + tree,form + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + Habits/Substance Abuse Tree + dental.habits.abuse + + + + + + + + + + + Habits/Substance Abuse + dental.habits.abuse + +
+ +
+

+ +

+ + + +
+
+ +
+ + + +
+
+
+
+
\ No newline at end of file diff --git a/dental/views/history_views.xml b/dental/views/history_views.xml new file mode 100644 index 0000000000..a2a7e1abf5 --- /dev/null +++ b/dental/views/history_views.xml @@ -0,0 +1,172 @@ + + + History + dental.history + tree,form + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + History Tree + dental.history + + + + + + + + + + + + Create History + dental.history + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
18
+
17
+
16
+
15
+
14
+
13
+
12
+
11
+
28
+
27
+
26
+
25
+
24
+
23
+
22
+
21
+
+
+ + + + + + + + + + + + + + + + + + + +
+
38
+
37
+
36
+
35
+
34
+
33
+
32
+
31
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+
+
+
+ +
\ No newline at end of file diff --git a/dental/views/medical_aids_views.xml b/dental/views/medical_aids_views.xml new file mode 100644 index 0000000000..fae39f445b --- /dev/null +++ b/dental/views/medical_aids_views.xml @@ -0,0 +1,54 @@ + + + Medical Aids + dental.medical.aid + tree,form + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + + Medical Aids + dental.medical.aid + +
+
+ +
+ +
+

+ + +

+ + + + + + + +
+ + + + +
+
+
+ + +
+
+ + + +
+
+
+
+
\ No newline at end of file diff --git a/dental/views/medication_views.xml b/dental/views/medication_views.xml new file mode 100644 index 0000000000..b87a9e2019 --- /dev/null +++ b/dental/views/medication_views.xml @@ -0,0 +1,50 @@ + + + Medication + dental.medication + tree,form + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + Medication + dental.medication + + + + + + + + + + + Medication + dental.medication + +
+ +
+

+ +

+ + + +
+
+ +
+ + + +
+
+
+
+ +
\ No newline at end of file diff --git a/dental/views/menuitem.xml b/dental/views/menuitem.xml new file mode 100644 index 0000000000..9e1574c580 --- /dev/null +++ b/dental/views/menuitem.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dental/views/patient_views.xml b/dental/views/patient_views.xml new file mode 100644 index 0000000000..7b7e623036 --- /dev/null +++ b/dental/views/patient_views.xml @@ -0,0 +1,178 @@ + + + Patients + dental.patients + kanban,tree,form + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + + Patients Tree + dental.patients + + + + + + + + + + + + + + Patient + dental.patients + +
+
+
+ +
+

+ + +

+ + + + +
+ + +
+ + + + + + + + + + + + + + +
+ + + + + + +
+ +
+ + + + + +
+ + + + + +
+
+
+ + + + + + + + + + + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + +
+ + + + + +
+
+
+
+ + +
+ +
+ + + +
+
+
+
+ + + dental.patients.kanban + dental.patients + + + + + +
+
+
+ +
+
+
+

+ + + +

+
+
+
+
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/dental/views/tag_views.xml b/dental/views/tag_views.xml new file mode 100644 index 0000000000..2977405b97 --- /dev/null +++ b/dental/views/tag_views.xml @@ -0,0 +1,23 @@ + + + Tags + dental.tags + tree + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + + Tags Tree + dental.tags + + + + + + +
\ No newline at end of file diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 0000000000..317272f8ba --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import controller +from . import wizard diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 0000000000..88c7a4f320 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,28 @@ +{ + "name": "estate", + "version": "0.1", + "depends": ["base", "mail"], + "description": "Technical practice", + "installable": True, + "application": True, + "category": "Real Estate/Brokerage", + "data": [ + "security/security.xml", + "security/ir.model.access.csv", + "data/estate.property.type.csv", + "report/estate_property_templates.xml", + "report/estate_propert_web_template.xml", + "report/estate_property_report.xml", + "views/res_user_views.xml", + "wizard/estate_property_wizard.xml", + "views/estate_property_views.xml", + "views/estate_property_offer_view.xml", + "views/estate_property_type.xml", + "views/estate_property_tag_view.xml", + "views/estate_menus.xml", + ], + "demo": [ + "demo/estate_property_demo.xml", + ], + "license": "LGPL-3", +} diff --git a/estate/controller/__init__.py b/estate/controller/__init__.py new file mode 100644 index 0000000000..6273183157 --- /dev/null +++ b/estate/controller/__init__.py @@ -0,0 +1 @@ +from . import estate_property_controller diff --git a/estate/controller/estate_property_controller.py b/estate/controller/estate_property_controller.py new file mode 100644 index 0000000000..8861e5f4b3 --- /dev/null +++ b/estate/controller/estate_property_controller.py @@ -0,0 +1,55 @@ +import math +import logging +from odoo.http import Controller, request, route + + +class PropertyController(Controller): + + @route( + ["/properties", "/properties/page/"], + auth="public", + type="http", + website=True, + ) + def properties_controller(self, page=1, per_page=6, *args, **kwargs): + try: + property_recode = request.env["estate.property"] + page = int(page) + per_page = int(per_page) + offset = (page - 1) * per_page + limit = per_page + domain = [("state", "in", ["offer_recived", "offer_accepted"])] + properties = property_recode.search( + domain, + limit=limit, + offset=offset, + ) + total_property = properties.search_count(domain) + total_pages = math.ceil(total_property / per_page) + pager = { + "url": "/properties/page/" + str(page), + "total": total_property, + "current_page": page, + "total_page": total_pages, + "step": per_page, + } + values = {"properties": properties, "pager": pager} + return request.render("estate.estate_properties_web", values) + except Exception: + logging.exception() + + @route( + "/properties/", + auth="public", + type="http", + website=True, + methods=["GET"], + ) + def property_controller(self, property_id, *args, **kwargs): + try: + property_recode = request.env["estate.property"].browse(property_id) + return request.render( + "estate.estate_property_web", ({"property": property_recode}) + ) + except Exception: + logging.exception() diff --git a/estate/data/estate.property.type.csv b/estate/data/estate.property.type.csv new file mode 100644 index 0000000000..74b7279c13 --- /dev/null +++ b/estate/data/estate.property.type.csv @@ -0,0 +1,5 @@ +id,name +1,"Residential" +2,"Commercial" +3,"Industrial" +4,"Land" \ 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 0000000000..fbacac1eff --- /dev/null +++ b/estate/demo/estate_property_demo.xml @@ -0,0 +1,105 @@ + + + + Big Villa + new + A nice and big villa + 12345 + 2020-02-02 + 1600000 + 0.0 + 6 + 100 + 4 + True + True + 100000 + + south + + + + Trailer home + sold + Home in a trailer park + 54321 + 1970-01-01 + 100000 + 120000.0 + 1 + 10 + 4 + False + + + + + Bahubali Home + new + Home in a Ocean park + 0001 + 2030-01-01 + 50.0 + 0.0 + 1 + 10 + 4 + false + + + + + + + Small Villa + new + A nice and big villa + 12345 + 2020-02-02 + 1600 + 0.0 + 6 + 100 + 4 + True + True + 100000 + + south + + + + 16000000 + 14 + + + + + 150000000 + 14 + + + + + 10001000.0 + 14 + + + + + + + + + + + + + + + diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 0000000000..fd50743b4f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,5 @@ +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer +from . import res_user_model diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 0000000000..eb90c75646 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,133 @@ +from dateutil.relativedelta import relativedelta +from datetime import date +from odoo import api, fields, models +from odoo.exceptions import UserError, ValidationError + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property Data" + _inherit = ["mail.thread", "mail.activity.mixin"] + name = fields.Char(required=True) + company_id = fields.Many2one( + "res.company", + string="Company", + required=True, + readonly=False, + default=lambda self: self.env.company, + ) + description = fields.Char() + postcode = fields.Char() + date_availability = fields.Date( + copy=False, default=date.today() + relativedelta(months=3) + ) + expected_price = fields.Float(required=True) + selling_price = fields.Float(copy=False, readonly=True) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + active = fields.Boolean(default=True) + property_image = fields.Image("Property Image") + state = fields.Selection( + required=True, + copy=False, + default="new", + selection=[ + ("new", "New"), + ("offer_recived", "Offer Recieved"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("canceled", "Canceled"), + ], + tracking=True, + ) + garden_orientation = fields.Selection( + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ] + ) + _order = "id desc" + salesman_id = fields.Many2one( + "res.users", + string="Salesman", + default=lambda self: self.env.user, + ) + buyer_id = fields.Many2one( + "res.partner", + string="Buyer", + copy=False, + ) + tag_ids = fields.Many2many("estate.property.tag", string="Tags", ondelete="cascade") + offer_ids = fields.One2many("estate.property.offer", "property_id") + total = fields.Integer(compute="total_area") + best_offer = fields.Float(compute="best_offer_selete", store=True) + property_type_id = fields.Many2one("estate.property.type") + sql_constraints = [ + ( + "check_expected_price", + "CHECK(expected_price > 0)", + "The Expected Price of Property should be positive", + ), + ( + "check_selling_price", + "CHECK(selling_price >= 0 )", + "The Selling Price of Property should be Positive", + ), + ("name_uniq", "unique (name)", "Property name already exists!"), + ] + + @api.depends("living_area", "garden_area") + def total_area(self): + for recorde in self: + recorde.total = recorde.living_area + recorde.garden_area + + @api.depends("offer_ids") + def best_offer_selete(self): + temp = 0 + for offer in self.offer_ids: + if offer.price > temp: + temp = offer.price + self.best_offer = temp + + @api.onchange("garden") + def garden_change(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = "" + + def status_action_sold_button(self): + if self.state == "canceled": + raise UserError("Canceled property can't be sold") + else: + self.state = "sold" + + def status_action_canceled_button(self): + if self.state == "sold": + raise UserError("Sold property can't be canceled") + else: + self.state = "canceled" + + @api.constrains("selling_price", "expected_price") + def _check_date_end(self): + for record in self: + if record.selling_price > 0: + price_percent = (record.selling_price / record.expected_price) * 100 + if price_percent < 90: + raise ValidationError("Selling Price must be at least 90%") + + @api.ondelete(at_uninstall=False) + def _check_property_state(self): + for recod in self: + if recod.state == "new" or recod.state == "canceled": + continue + else: + raise ValidationError("New or Canceled property can be delete only.") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 0000000000..7fbe70d2d5 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,101 @@ +import logging +from datetime import date, timedelta +from odoo import api, fields, models +from odoo.exceptions import UserError, ValidationError + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer" + _order = "price desc" + price = fields.Float() + status = fields.Selection( + copy=False, selection=[("accepted", "Accepted"), ("refused", "Refused")] + ) + partner_id = fields.Many2one("res.partner", required=True) + property_id = fields.Many2one("estate.property", required=True) + validity = fields.Integer(default=7) + deadline = fields.Date(compute="_compute_deadline", inverse="_inverse_deadline") + property_type_id = fields.Many2one( + related="property_id.property_type_id", store=True + ) + + @api.ondelete(at_uninstall=False) + def _check_offer(self): + for record in self: + if record.status == "accepted": + record.property_id.selling_price = 0 + record.property_id.buyer_id = None + else: + pass + + @api.depends("validity") + def _compute_deadline(self): + for record in self: + created_date = record.create_date or date.today() + record.deadline = ( + created_date + timedelta(days=record.validity) + if record.validity + else created_date + ) + + def _inverse_deadline(self): + for record in self: + if record.deadline: + record.validity = ( + record.deadline - record.property_id.create_date.date() + ).days + else: + record.validity = 7 + + def action_status_accept(self): + self.status = "accepted" + price_percent = 0 + if self.property_id.expected_price != 0: + price_percent = (self.price / self.property_id.expected_price) * 100 + if price_percent > 90: + self.property_id.selling_price = self.price + self.property_id.buyer_id = self.partner_id + self.property_id.state = "offer_accepted" + property_id = self.property_id + try: + existing_offer = self.search([("property_id", "=", int(property_id))]) + for record in existing_offer: + if record.id == self.id: + continue + else: + record.status = "refused" + except (UserError, ValidationError): + logging.exception("Error while updating offer status accepted") + else: + raise ValidationError("The selling price must be at least 90%") + + def aciton_status_refused(self): + if self.status == "accepted": + self.property_id.selling_price = 0 + self.property_id.buyer_id = None + self.status = "refused" + else: + self.status = "refused" + + _sql_constraints = [ + ("check_price", "CHECK(price >= 0)", "The Price of Offer should be positive"), + ] + + @api.model + def create(self, vals): + property_id = vals.get("property_id") + price = vals.get("price", 0) + if not property_id: + raise ValidationError("Property ID is required.") + property_record = self.env["estate.property"].browse(property_id) + existing_offers = self.search([("property_id", "=", property_id)]) + max_price = max(existing_offers.mapped("price"), default=0) + if price < max_price: + raise UserError("The offer price must be higher than the existing offers.") + record = super().create(vals) + if property_record: + property_record.state = "offer_recived" + else: + raise ValidationError("Property record could not be found.") + return record diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 0000000000..8c5b4f077d --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'Estate Property tag' + name = fields.Char(required=True) + color = fields.Integer() + _order = 'name' + _sql_constraints = [('name_uniq', 'unique (name)', "Tag name already exists!")] diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 0000000000..dcb84dbbcf --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,20 @@ +from odoo import fields, models, api + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Real Estate Property Type" + name = fields.Char(required=True) + property_id = fields.One2many('estate.property', 'property_type_id') + _order = 'sequence, name' + sequence = fields.Integer('Sequence', help="Used to order stages. Lower is better.") + offers_ids = fields.One2many('estate.property.offer', 'property_type_id') + offer_count = fields.Integer(compute='_offers_count', help="Number of offers") + + @api.depends('offers_ids.property_type_id') + def _offers_count(self): + for record in self: + if record.offers_ids: + record.offer_count = len(record.offers_ids) + else: + record.offer_count = 0 diff --git a/estate/models/res_user_model.py b/estate/models/res_user_model.py new file mode 100644 index 0000000000..fd93b6c81d --- /dev/null +++ b/estate/models/res_user_model.py @@ -0,0 +1,6 @@ +from odoo import models, fields + + +class ResUserModel(models.Model): + _inherit = 'res.users' + property_ids = fields.One2many('estate.property', 'salesman_id', string='Properties', domain="['|', ('state', '=', 'new'),('state', '=', 'offer_recived')]") diff --git a/estate/report/estate_propert_web_template.xml b/estate/report/estate_propert_web_template.xml new file mode 100644 index 0000000000..9d82843aab --- /dev/null +++ b/estate/report/estate_propert_web_template.xml @@ -0,0 +1,180 @@ + + + + + diff --git a/estate/report/estate_property_report.xml b/estate/report/estate_property_report.xml new file mode 100644 index 0000000000..1f7ce71063 --- /dev/null +++ b/estate/report/estate_property_report.xml @@ -0,0 +1,24 @@ + + + Real Estate Property + estate.property + qweb-pdf + estate.report_property_template + estate.report_property_template + 'Estate Property- %s' % (object.name or 'Attendee').replace('/','') + + report + + + + Salesman Property + res.users + qweb-pdf + estate.report_salesmen_property_template + estate.report_salesmen_property_template + 'Properties - %s' % (object.name or 'Attendee').replace('/','') + + report + + + \ 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 0000000000..188ad9624b --- /dev/null +++ b/estate/report/estate_property_templates.xml @@ -0,0 +1,102 @@ + + + + + + + \ 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 0000000000..e7c0fdd6ec --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,16 @@ +"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,0,0,0 +estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,0,0,0 +estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,0,0,0 +estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,0,0,0 +estate.access_add_offer,access_add_offer,estate.model_add_offer,base.group_user,1,0,0,0 + +access_estate_property_group_user,access_estate_property_group_user,model_estate_property,estate.estate_group_user,1,1,1,0 +estate.access_estate_property_type_group_user,access_estate_property_type_group_user,estate.model_estate_property_type,estate.estate_group_user,1,0,0,0 +estate.access_estate_property_tag_group_user,access_estate_property_tag_group_user,estate.model_estate_property_tag,estate.estate_group_user,1,0,0,0 + +access_estate_property_manager,access_estate_property_manager,model_estate_property,estate.estate_group_manager,1,1,1,0 +estate.access_estate_property_type_manager,access_estate_property_type_manager,estate.model_estate_property_type,estate.estate_group_manager,1,1,1,1 +estate.access_estate_property_tag_manager,access_estate_property_tag_manager,estate.model_estate_property_tag,estate.estate_group_manager,1,1,1,1 +estate.access_estate_property_offer_manager,access_estate_property_offer_manager,estate.model_estate_property_offer,estate.estate_group_manager,1,1,1,1 +estate.access_add_offer_manager,access_add_offer_manager,estate.model_add_offer,estate.estate_group_manager,1,1,1,1 diff --git a/estate/security/security.xml b/estate/security/security.xml new file mode 100644 index 0000000000..37670fb473 --- /dev/null +++ b/estate/security/security.xml @@ -0,0 +1,38 @@ + + + Agent + + + + Manager + + + + + + + Agent permissions + + + ['|', ('salesman_id', '=', user.id), ('salesman_id', '=', False)] + + + + + Manager permissions + + + + + + Multi Compaines + + + [ + '|', ('company_id', '=', False), + ('company_id', 'in', company_ids)] + + + + + diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 0000000000..583eeb78a4 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml new file mode 100644 index 0000000000..f04d9c9310 --- /dev/null +++ b/estate/views/estate_property_offer_view.xml @@ -0,0 +1,47 @@ + + + + estate_property_offer + estate.property.offer + + + + + + + +

+ +

+ + + + + + + + + + + + + +
+
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 0000000000..6763456a83 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,165 @@ + + + Property + estate.property + kanban,tree,form + {'search_default_state': True, 'search_default_current': True} + +

+ Create a new Property product +

+
+
+ + + estate_property_tree + estate.property + + + +
+
+ + + + + + + + + + +
+
+
+ + + estate_property_form + estate.property + +
+
+ +
+ +
+

+ + + + +

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + + state_property_search + estate.property + + + + + + + + + + + + + + + + + + + + estate.property.kanban + estate.property + + + + + +
+

+ +

+
+
+ Expected Price: + +
+
+ Best Offer: + +
+
+ Selling Price: + +
+ + +
+
+
+
+
+
+
+
diff --git a/estate/views/res_user_views.xml b/estate/views/res_user_views.xml new file mode 100644 index 0000000000..da794f7d4f --- /dev/null +++ b/estate/views/res_user_views.xml @@ -0,0 +1,29 @@ + + + + + res.users.form.inherit.properties + res.users + + + + + + + + + + + + + + + + + + + + + + + diff --git a/estate/wizard/__init__.py b/estate/wizard/__init__.py new file mode 100644 index 0000000000..8dd2815351 --- /dev/null +++ b/estate/wizard/__init__.py @@ -0,0 +1 @@ +from . import estate_property_add_offer diff --git a/estate/wizard/estate_property_add_offer.py b/estate/wizard/estate_property_add_offer.py new file mode 100644 index 0000000000..7d7fdcfa96 --- /dev/null +++ b/estate/wizard/estate_property_add_offer.py @@ -0,0 +1,26 @@ +from odoo import fields, models + + +class AddOffer(models.TransientModel): + _name = "add.offer" + _description = "Add Offer in mutiple property" + price = fields.Float() + validity = fields.Integer(default=7) + buyer_id = fields.Many2one( + "res.partner", + string="Buyer", + copy=False, + ) + + def add_offer(self): + context = self.env.context + active_ids = context.get("active_ids", []) + for recod in active_ids: + self.env["estate.property.offer"].create( + { + "property_id": recod, + "price": self.price, + "validity": self.validity, + "partner_id": self.buyer_id.id, + } + ) diff --git a/estate/wizard/estate_property_wizard.xml b/estate/wizard/estate_property_wizard.xml new file mode 100644 index 0000000000..f733907f13 --- /dev/null +++ b/estate/wizard/estate_property_wizard.xml @@ -0,0 +1,29 @@ + + + + estate_property_offer_form + add.offer + +
+ + + + + +
+
+
+
+
+ + + Add Offer + add.offer + 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..451b733e7d --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': "estate_account", + 'version': '0.1', + 'depends': ['base', 'account', 'estate'], + 'description': "Technical practice", + 'data': [ + "report/estate_account_invoice_templates.xml", + ], + 'installable': True, + 'application': True, + 'license': 'LGPL-3', +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 0000000000..5e1963c9d2 --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 0000000000..aac21ec8d8 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,44 @@ +from odoo import models, Command +import logging +from datetime import date + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def status_action_sold_button(self): + logging.info("Request to create the invoices of property") + self.check_access_rights('write') + self.check_access_rule('write') + for record in self: + if record.buyer_id: + partner_id = record.buyer_id.id + self.env["account.move"].sudo().create( + { + "partner_id": partner_id, + "move_type": "out_invoice", + "invoice_date": date.today(), + "narration": "Tutorials traning", + "amount_total": record.selling_price, + "invoice_line_ids": [ + Command.create( + { + "name": record.name, + "quantity": 1, + "price_unit": record.selling_price * 0.06, + "invoice_date": date.today(), + } + ), + Command.create( + { + "name": "Administrative Fee", + "quantity": 1, + "price_unit": 100, + "invoice_date": date.today(), + }, + ), + ], + } + ) + logging.info("Successfully created") + return super().status_action_sold_button() diff --git a/estate_account/report/estate_account_invoice_templates.xml b/estate_account/report/estate_account_invoice_templates.xml new file mode 100644 index 0000000000..dc94894c81 --- /dev/null +++ b/estate_account/report/estate_account_invoice_templates.xml @@ -0,0 +1,11 @@ + + + diff --git a/estate_meeting/__init__.py b/estate_meeting/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/estate_meeting/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_meeting/__manifest__.py b/estate_meeting/__manifest__.py new file mode 100644 index 0000000000..85ace34759 --- /dev/null +++ b/estate_meeting/__manifest__.py @@ -0,0 +1,13 @@ +{ + "name": "estate_meeting", + "version": "0.1", + "depends": ['base', 'estate', 'calendar'], + "description": "Meeting wizard betwwen calender and estate", + "data": [ + "security/ir.model.access.csv", + "views/estate_meeting_view.xml", + ], + "installable": True, + "application": True, + "license": "LGPL-3", +} diff --git a/estate_meeting/models/__init__.py b/estate_meeting/models/__init__.py new file mode 100644 index 0000000000..dedf335ff4 --- /dev/null +++ b/estate_meeting/models/__init__.py @@ -0,0 +1 @@ +from . import estate_meeting diff --git a/estate_meeting/models/estate_meeting.py b/estate_meeting/models/estate_meeting.py new file mode 100644 index 0000000000..b20407f209 --- /dev/null +++ b/estate_meeting/models/estate_meeting.py @@ -0,0 +1,102 @@ +import logging +from datetime import datetime, timedelta +from odoo import api, fields, models + + +class EstateMeetings(models.TransientModel): + _name = "estate.meetings" + _description = "Estate Meeting" + + @api.model + def default_get(self, fields_list): + # breakpoint() + res = super().default_get(fields_list) + proper_id = self.env.context["active_id"] + logging.info(proper_id) + proper = self.env["estate.property"].browse(proper_id) + logging.info(proper) + partner = self.env["res.partner"].browse(proper.salesman_id.partner_id.id) + logging.info(partner) + # salsesman = + # res["attendee_ids"] = proper.salesman_id.id + res["attendee_ids"] = partner + logging.info(res) + return res + + @api.model + def _default_start(self): + now = fields.Datetime.now() + return now + (datetime.min - now) % timedelta(minutes=30) + + @api.model + def _default_stop(self): + now = fields.Datetime.now() + start = now + (datetime.min - now) % timedelta(minutes=30) + return start + timedelta(hours=1) + + name = fields.Char("Meeting Subject", required=True) + description = fields.Html("Description") + user_id = fields.Many2one( + "res.users", "Organizer", default=lambda self: self.env.user + ) + start = fields.Datetime( + "Start", + required=True, + tracking=True, + default=_default_start, + help="Start date of an event, without time for full days events", + ) + stop = fields.Datetime( + "Stop", + required=True, + tracking=True, + default=_default_stop, + compute="_compute_stop", + readonly=False, + store=True, + help="Stop date of an event, without time for full days events", + ) + duration = fields.Float( + "Duration", compute="_compute_duration", store=True, readonly=False + ) + allday = fields.Boolean("All Day", default=False) + attendee_ids = fields.Many2one("res.partner", readyonly=True) + + def _get_duration(self, start, stop): + """Get the duration value between the 2 given dates.""" + if not start or not stop: + return 0 + duration = (stop - start).total_seconds() / 3600 + return round(duration, 2) + + @api.depends("stop", "start") + def _compute_duration(self): + for event in self: + event.duration = self._get_duration(event.start, event.stop) + + @api.depends("start", "duration") + def _compute_stop(self): + duration_field = self._fields["duration"] + self.env.remove_to_compute(duration_field, self) + for event in self: + # Round the duration (in hours) to the minute to avoid weird situations where the event + # stops at 4:19:59, later displayed as 4:19. + event.stop = event.start and event.start + timedelta( + minutes=round((event.duration or 1.0) * 60) + ) + if event.allday: + event.stop -= timedelta(seconds=1) + + def action_create_meeting(self): + logging.info(self.attendee_ids) + self.env["calendar.event"].create( + { + "name": self.name, + "description": self.description, + "user_id": self.user_id.id, + "start": self.start, + "stop": self.stop, + "allday": self.allday, + "partner_ids": self.attendee_ids, + } + ) diff --git a/estate_meeting/security/ir.model.access.csv b/estate_meeting/security/ir.model.access.csv new file mode 100644 index 0000000000..6ea58cf351 --- /dev/null +++ b/estate_meeting/security/ir.model.access.csv @@ -0,0 +1,2 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_estate_property,access_estate_property,model_estate_meetings,base.group_user,1,1,1,1 diff --git a/estate_meeting/views/estate_meeting_view.xml b/estate_meeting/views/estate_meeting_view.xml new file mode 100644 index 0000000000..075340a1bb --- /dev/null +++ b/estate_meeting/views/estate_meeting_view.xml @@ -0,0 +1,42 @@ + + + estate_meeting_form + estate.meetings + +
+ + + + + + + + +
+
+
+
+
+ + + Estate Meeting + estate.meetings + form + + new + + + + estate.property.form.inherit + estate.property + + + +