From 43e76b2e8b2a5936179da97ea33389b09fbc4cf3 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:04:50 +0700 Subject: [PATCH] Add initial Odoo FSM modules and deployment make targets --- .gitignore | 3 + Makefile | 117 +++++++++++++++++ odoo/Dockerfile | 7 + .../addons/dsrpt_repair_customers/__init__.py | 1 + .../dsrpt_repair_customers/__manifest__.py | 14 ++ .../dsrpt_repair_customers/models/__init__.py | 1 + .../dsrpt_repair_customers/models/customer.py | 42 ++++++ .../security/ir.model.access.csv | 4 + .../views/customer_views.xml | 57 ++++++++ odoo/addons/dsrpt_repair_main/__init__.py | 1 + odoo/addons/dsrpt_repair_main/__manifest__.py | 16 +++ .../dsrpt_repair_main/models/__init__.py | 1 + .../dsrpt_repair_main/models/fsm_zone.py | 11 ++ .../security/ir.model.access.csv | 2 + .../views/fsm_zone_views.xml | 38 ++++++ odoo/addons/dsrpt_repair_main/views/menu.xml | 5 + .../addons/dsrpt_repair_materials/__init__.py | 1 + .../dsrpt_repair_materials/__manifest__.py | 14 ++ .../dsrpt_repair_materials/models/__init__.py | 1 + .../dsrpt_repair_materials/models/material.py | 13 ++ .../security/ir.model.access.csv | 2 + .../views/material_views.xml | 42 ++++++ .../dsrpt_repair_technicians/__init__.py | 1 + .../dsrpt_repair_technicians/__manifest__.py | 14 ++ .../models/__init__.py | 1 + .../models/technician.py | 66 ++++++++++ .../security/ir.model.access.csv | 4 + .../views/technician_views.xml | 62 +++++++++ .../dsrpt_repair_work_orders/__init__.py | 1 + .../dsrpt_repair_work_orders/__manifest__.py | 22 ++++ .../data/sequence.xml | 9 ++ .../models/__init__.py | 1 + .../models/work_order.py | 123 ++++++++++++++++++ .../security/ir.model.access.csv | 4 + .../views/work_order_views.xml | 92 +++++++++++++ 35 files changed, 793 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 odoo/addons/dsrpt_repair_customers/__init__.py create mode 100644 odoo/addons/dsrpt_repair_customers/__manifest__.py create mode 100644 odoo/addons/dsrpt_repair_customers/models/__init__.py create mode 100644 odoo/addons/dsrpt_repair_customers/models/customer.py create mode 100644 odoo/addons/dsrpt_repair_customers/security/ir.model.access.csv create mode 100644 odoo/addons/dsrpt_repair_customers/views/customer_views.xml create mode 100644 odoo/addons/dsrpt_repair_main/__init__.py create mode 100644 odoo/addons/dsrpt_repair_main/__manifest__.py create mode 100644 odoo/addons/dsrpt_repair_main/models/__init__.py create mode 100644 odoo/addons/dsrpt_repair_main/models/fsm_zone.py create mode 100644 odoo/addons/dsrpt_repair_main/security/ir.model.access.csv create mode 100644 odoo/addons/dsrpt_repair_main/views/fsm_zone_views.xml create mode 100644 odoo/addons/dsrpt_repair_main/views/menu.xml create mode 100644 odoo/addons/dsrpt_repair_materials/__init__.py create mode 100644 odoo/addons/dsrpt_repair_materials/__manifest__.py create mode 100644 odoo/addons/dsrpt_repair_materials/models/__init__.py create mode 100644 odoo/addons/dsrpt_repair_materials/models/material.py create mode 100644 odoo/addons/dsrpt_repair_materials/security/ir.model.access.csv create mode 100644 odoo/addons/dsrpt_repair_materials/views/material_views.xml create mode 100644 odoo/addons/dsrpt_repair_technicians/__init__.py create mode 100644 odoo/addons/dsrpt_repair_technicians/__manifest__.py create mode 100644 odoo/addons/dsrpt_repair_technicians/models/__init__.py create mode 100644 odoo/addons/dsrpt_repair_technicians/models/technician.py create mode 100644 odoo/addons/dsrpt_repair_technicians/security/ir.model.access.csv create mode 100644 odoo/addons/dsrpt_repair_technicians/views/technician_views.xml create mode 100644 odoo/addons/dsrpt_repair_work_orders/__init__.py create mode 100644 odoo/addons/dsrpt_repair_work_orders/__manifest__.py create mode 100644 odoo/addons/dsrpt_repair_work_orders/data/sequence.xml create mode 100644 odoo/addons/dsrpt_repair_work_orders/models/__init__.py create mode 100644 odoo/addons/dsrpt_repair_work_orders/models/work_order.py create mode 100644 odoo/addons/dsrpt_repair_work_orders/security/ir.model.access.csv create mode 100644 odoo/addons/dsrpt_repair_work_orders/views/work_order_views.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2348c91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +__pycache__/ +*.pyc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b371862 --- /dev/null +++ b/Makefile @@ -0,0 +1,117 @@ +ODOO_SERVICE_PREFIX ?= sandbox-odoorepair-gfijxl +MODULES ?= all +CUSTOM_MODULES ?= dsrpt_repair_main,dsrpt_repair_customers,dsrpt_repair_technicians,dsrpt_repair_materials,dsrpt_repair_work_orders +ODOO_CONFIG ?= /etc/odoo/odoo.conf + +ODOO_DB_NAME ?= +ODOO_DB_USER ?= +ODOO_DB_PASSWORD ?= +ODOO_DB_HOST ?= +ODOO_DB_PORT ?= + +GIT_REMOTE ?= origin +GIT_BRANCH ?= main + +.PHONY: help install-modules update-modules install-custom update-custom restart-service logs shell repo-status repo-pull repo-set-ssh repo-set-https + +help: + @echo "Targets:" + @echo " make install-modules MODULES=module_a,module_b" + @echo " make update-modules MODULES=module_a,module_b" + @echo " make install-custom" + @echo " make update-custom" + @echo " make restart-service" + @echo " make logs" + @echo " make shell" + @echo " make repo-status" + @echo " make repo-set-ssh" + @echo " make repo-set-https" + @echo " make repo-pull GIT_BRANCH=main" + +install-modules: + @container="$$(docker ps --filter 'name=$(ODOO_SERVICE_PREFIX)' --format '{{.Names}}' | head -1)"; \ + if [ -z "$$container" ]; then \ + echo "No Odoo container found for prefix: $(ODOO_SERVICE_PREFIX)"; \ + exit 1; \ + fi; \ + db_name="$(ODOO_DB_NAME)"; \ + db_user="$(ODOO_DB_USER)"; \ + db_password="$(ODOO_DB_PASSWORD)"; \ + db_host="$(ODOO_DB_HOST)"; \ + db_port="$(ODOO_DB_PORT)"; \ + if [ -z "$$db_name" ]; then db_name="$$(docker exec $$container printenv ODOO_DB_NAME 2>/dev/null || true)"; fi; \ + if [ -z "$$db_user" ]; then db_user="$$(docker exec $$container printenv ODOO_DB_USER 2>/dev/null || true)"; fi; \ + if [ -z "$$db_password" ]; then db_password="$$(docker exec $$container printenv ODOO_DB_PASSWORD 2>/dev/null || true)"; fi; \ + if [ -z "$$db_host" ]; then db_host="$$(docker exec $$container printenv ODOO_DB_HOST 2>/dev/null || true)"; fi; \ + if [ -z "$$db_port" ]; then db_port="$$(docker exec $$container printenv ODOO_DB_PORT 2>/dev/null || true)"; fi; \ + if [ -z "$$db_name" ] || [ -z "$$db_user" ] || [ -z "$$db_password" ] || [ -z "$$db_host" ]; then \ + echo "Cannot resolve ODOO_DB_* values. Set them in env or Make vars."; \ + exit 1; \ + fi; \ + db_port_arg=""; \ + if [ -n "$$db_port" ]; then db_port_arg="--db_port=$$db_port"; fi; \ + echo "Installing modules: $(MODULES)"; \ + docker exec $$container odoo -c '$(ODOO_CONFIG)' -d "$$db_name" -r "$$db_user" -w "$$db_password" --db_host="$$db_host" $$db_port_arg -i '$(MODULES)' --stop-after-init; \ + $(MAKE) restart-service + +update-modules: + @container="$$(docker ps --filter 'name=$(ODOO_SERVICE_PREFIX)' --format '{{.Names}}' | head -1)"; \ + if [ -z "$$container" ]; then \ + echo "No Odoo container found for prefix: $(ODOO_SERVICE_PREFIX)"; \ + exit 1; \ + fi; \ + db_name="$(ODOO_DB_NAME)"; \ + db_user="$(ODOO_DB_USER)"; \ + db_password="$(ODOO_DB_PASSWORD)"; \ + db_host="$(ODOO_DB_HOST)"; \ + db_port="$(ODOO_DB_PORT)"; \ + if [ -z "$$db_name" ]; then db_name="$$(docker exec $$container printenv ODOO_DB_NAME 2>/dev/null || true)"; fi; \ + if [ -z "$$db_user" ]; then db_user="$$(docker exec $$container printenv ODOO_DB_USER 2>/dev/null || true)"; fi; \ + if [ -z "$$db_password" ]; then db_password="$$(docker exec $$container printenv ODOO_DB_PASSWORD 2>/dev/null || true)"; fi; \ + if [ -z "$$db_host" ]; then db_host="$$(docker exec $$container printenv ODOO_DB_HOST 2>/dev/null || true)"; fi; \ + if [ -z "$$db_port" ]; then db_port="$$(docker exec $$container printenv ODOO_DB_PORT 2>/dev/null || true)"; fi; \ + if [ -z "$$db_name" ] || [ -z "$$db_user" ] || [ -z "$$db_password" ] || [ -z "$$db_host" ]; then \ + echo "Cannot resolve ODOO_DB_* values. Set them in env or Make vars."; \ + exit 1; \ + fi; \ + db_port_arg=""; \ + if [ -n "$$db_port" ]; then db_port_arg="--db_port=$$db_port"; fi; \ + echo "Updating modules: $(MODULES)"; \ + docker exec $$container odoo -c '$(ODOO_CONFIG)' -d "$$db_name" -r "$$db_user" -w "$$db_password" --db_host="$$db_host" $$db_port_arg -u '$(MODULES)' --stop-after-init; \ + $(MAKE) restart-service + +install-custom: + @$(MAKE) install-modules MODULES='$(CUSTOM_MODULES)' + +update-custom: + @$(MAKE) update-modules MODULES='$(CUSTOM_MODULES)' + +restart-service: + @service="$$(docker service ls --format '{{.Name}}' | grep -m1 '^$(ODOO_SERVICE_PREFIX)')"; \ + if [ -z "$$service" ]; then \ + echo "No service found for prefix: $(ODOO_SERVICE_PREFIX)"; \ + exit 1; \ + fi; \ + echo "Restarting service $$service"; \ + docker service update --force $$service >/dev/null + +logs: + @service="$$(docker service ls --format '{{.Name}}' | grep -m1 '^$(ODOO_SERVICE_PREFIX)')"; \ + if [ -z "$$service" ]; then \ + echo "No service found for prefix: $(ODOO_SERVICE_PREFIX)"; \ + exit 1; \ + fi; \ + docker service logs --tail 100 -f $$service + +shell: + @container="$$(docker ps --filter 'name=$(ODOO_SERVICE_PREFIX)' --format '{{.Names}}' | head -1)"; \ + if [ -z "$$container" ]; then \ + echo "No Odoo container found for prefix: $(ODOO_SERVICE_PREFIX)"; \ + exit 1; \ + fi; \ + docker exec -it $$container bash + +repo-status: ; @git remote -v && git branch --show-current && git status -sb +repo-pull: ; @git fetch $(GIT_REMOTE) && git pull --ff-only $(GIT_REMOTE) $(GIT_BRANCH) +repo-set-ssh: ; @git remote set-url origin git@gitea-repair:dsrptlab/repair.git && git remote -v +repo-set-https: ; @git remote set-url origin https://gitea.dsrptlab.com/dsrptlab/repair.git && git remote -v diff --git a/odoo/Dockerfile b/odoo/Dockerfile index 39addf3..9351bb2 100644 --- a/odoo/Dockerfile +++ b/odoo/Dockerfile @@ -84,6 +84,13 @@ RUN mkdir -p /var/lib/odoo /mnt/extra-addons \ && chown -R odoo:odoo /var/lib/odoo /mnt/extra-addons \ && chmod -R u+w /var/lib/odoo +# Make custom addons available in the image +COPY ./odoo/addons /mnt/extra-addons +RUN chown -R odoo:odoo /mnt/extra-addons \ + && if grep -q '^addons_path' /etc/odoo/odoo.conf; then \ + sed -i 's|^addons_path *= *|addons_path = /mnt/extra-addons,|' /etc/odoo/odoo.conf; \ + fi + # Build self-contained entrypoint in image (no external files required) RUN printf '%s\n' \ '#!/usr/bin/env bash' \ diff --git a/odoo/addons/dsrpt_repair_customers/__init__.py b/odoo/addons/dsrpt_repair_customers/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/odoo/addons/dsrpt_repair_customers/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo/addons/dsrpt_repair_customers/__manifest__.py b/odoo/addons/dsrpt_repair_customers/__manifest__.py new file mode 100644 index 0000000..32158dd --- /dev/null +++ b/odoo/addons/dsrpt_repair_customers/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "DSRPT Repair Customers", + "summary": "Customers, contacts, and addresses", + "version": "19.0.1.0.0", + "category": "Services", + "author": "DisruptLab", + "license": "LGPL-3", + "depends": ["base", "dsrpt_repair_main"], + "data": [ + "security/ir.model.access.csv", + "views/customer_views.xml", + ], + "installable": True, +} diff --git a/odoo/addons/dsrpt_repair_customers/models/__init__.py b/odoo/addons/dsrpt_repair_customers/models/__init__.py new file mode 100644 index 0000000..913f2ab --- /dev/null +++ b/odoo/addons/dsrpt_repair_customers/models/__init__.py @@ -0,0 +1 @@ +from . import customer diff --git a/odoo/addons/dsrpt_repair_customers/models/customer.py b/odoo/addons/dsrpt_repair_customers/models/customer.py new file mode 100644 index 0000000..c1152c1 --- /dev/null +++ b/odoo/addons/dsrpt_repair_customers/models/customer.py @@ -0,0 +1,42 @@ +from odoo import fields, models + + +class RepairCustomer(models.Model): + _name = "repair.customer" + _description = "Repair Customer" + _order = "name" + + name = fields.Char(required=True) + zone_id = fields.Many2one("repair.fsm.zone", string="FSM Zone") + note = fields.Text() + contact_ids = fields.One2many("repair.customer.contact", "customer_id", string="Contacts") + address_ids = fields.One2many("repair.customer.address", "customer_id", string="Addresses") + + +class RepairCustomerContact(models.Model): + _name = "repair.customer.contact" + _description = "Repair Customer Contact" + + customer_id = fields.Many2one("repair.customer", required=True, ondelete="cascade") + contact_type = fields.Selection( + selection=[ + ("phone", "Phone"), + ("email", "Email"), + ("telegram", "Telegram"), + ("other", "Other"), + ], + required=True, + default="phone", + ) + value = fields.Char(required=True) + + +class RepairCustomerAddress(models.Model): + _name = "repair.customer.address" + _description = "Repair Customer Address" + + customer_id = fields.Many2one("repair.customer", required=True, ondelete="cascade") + label = fields.Char(required=True, default="Service Address") + street = fields.Char(required=True) + zone_id = fields.Many2one("repair.fsm.zone", string="FSM Zone") + details = fields.Text() diff --git a/odoo/addons/dsrpt_repair_customers/security/ir.model.access.csv b/odoo/addons/dsrpt_repair_customers/security/ir.model.access.csv new file mode 100644 index 0000000..b555cfa --- /dev/null +++ b/odoo/addons/dsrpt_repair_customers/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_repair_customer_user,repair.customer user,model_repair_customer,base.group_user,1,1,1,1 +access_repair_customer_contact_user,repair.customer.contact user,model_repair_customer_contact,base.group_user,1,1,1,1 +access_repair_customer_address_user,repair.customer.address user,model_repair_customer_address,base.group_user,1,1,1,1 diff --git a/odoo/addons/dsrpt_repair_customers/views/customer_views.xml b/odoo/addons/dsrpt_repair_customers/views/customer_views.xml new file mode 100644 index 0000000..e51e667 --- /dev/null +++ b/odoo/addons/dsrpt_repair_customers/views/customer_views.xml @@ -0,0 +1,57 @@ + + + + repair.customer.tree + repair.customer + + + + + + + + + + repair.customer.form + repair.customer + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + Customers + repair.customer + list,form + + + +
diff --git a/odoo/addons/dsrpt_repair_main/__init__.py b/odoo/addons/dsrpt_repair_main/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/odoo/addons/dsrpt_repair_main/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo/addons/dsrpt_repair_main/__manifest__.py b/odoo/addons/dsrpt_repair_main/__manifest__.py new file mode 100644 index 0000000..94b23fb --- /dev/null +++ b/odoo/addons/dsrpt_repair_main/__manifest__.py @@ -0,0 +1,16 @@ +{ + "name": "DSRPT Repair Main", + "summary": "Core entities for Repair FSM", + "version": "19.0.1.0.0", + "category": "Services", + "author": "DisruptLab", + "license": "LGPL-3", + "depends": ["base"], + "data": [ + "security/ir.model.access.csv", + "views/menu.xml", + "views/fsm_zone_views.xml", + ], + "installable": True, + "application": True, +} diff --git a/odoo/addons/dsrpt_repair_main/models/__init__.py b/odoo/addons/dsrpt_repair_main/models/__init__.py new file mode 100644 index 0000000..588c333 --- /dev/null +++ b/odoo/addons/dsrpt_repair_main/models/__init__.py @@ -0,0 +1 @@ +from . import fsm_zone diff --git a/odoo/addons/dsrpt_repair_main/models/fsm_zone.py b/odoo/addons/dsrpt_repair_main/models/fsm_zone.py new file mode 100644 index 0000000..dafd9f7 --- /dev/null +++ b/odoo/addons/dsrpt_repair_main/models/fsm_zone.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class RepairFsmZone(models.Model): + _name = "repair.fsm.zone" + _description = "FSM Zone" + _order = "name" + + name = fields.Char(required=True) + code = fields.Char() + active = fields.Boolean(default=True) diff --git a/odoo/addons/dsrpt_repair_main/security/ir.model.access.csv b/odoo/addons/dsrpt_repair_main/security/ir.model.access.csv new file mode 100644 index 0000000..3a8062d --- /dev/null +++ b/odoo/addons/dsrpt_repair_main/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_repair_fsm_zone_user,repair.fsm.zone user,model_repair_fsm_zone,base.group_user,1,1,1,1 diff --git a/odoo/addons/dsrpt_repair_main/views/fsm_zone_views.xml b/odoo/addons/dsrpt_repair_main/views/fsm_zone_views.xml new file mode 100644 index 0000000..0a0bf54 --- /dev/null +++ b/odoo/addons/dsrpt_repair_main/views/fsm_zone_views.xml @@ -0,0 +1,38 @@ + + + + repair.fsm.zone.tree + repair.fsm.zone + + + + + + + + + + + repair.fsm.zone.form + repair.fsm.zone + +
+ + + + + + + +
+
+
+ + + FSM Zones + repair.fsm.zone + list,form + + + +
diff --git a/odoo/addons/dsrpt_repair_main/views/menu.xml b/odoo/addons/dsrpt_repair_main/views/menu.xml new file mode 100644 index 0000000..dac57f2 --- /dev/null +++ b/odoo/addons/dsrpt_repair_main/views/menu.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/odoo/addons/dsrpt_repair_materials/__init__.py b/odoo/addons/dsrpt_repair_materials/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/odoo/addons/dsrpt_repair_materials/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo/addons/dsrpt_repair_materials/__manifest__.py b/odoo/addons/dsrpt_repair_materials/__manifest__.py new file mode 100644 index 0000000..3f0a656 --- /dev/null +++ b/odoo/addons/dsrpt_repair_materials/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "DSRPT Repair Materials", + "summary": "Materials for work orders", + "version": "19.0.1.0.0", + "category": "Services", + "author": "DisruptLab", + "license": "LGPL-3", + "depends": ["base", "dsrpt_repair_main"], + "data": [ + "security/ir.model.access.csv", + "views/material_views.xml", + ], + "installable": True, +} diff --git a/odoo/addons/dsrpt_repair_materials/models/__init__.py b/odoo/addons/dsrpt_repair_materials/models/__init__.py new file mode 100644 index 0000000..3d46e4d --- /dev/null +++ b/odoo/addons/dsrpt_repair_materials/models/__init__.py @@ -0,0 +1 @@ +from . import material diff --git a/odoo/addons/dsrpt_repair_materials/models/material.py b/odoo/addons/dsrpt_repair_materials/models/material.py new file mode 100644 index 0000000..0ffc854 --- /dev/null +++ b/odoo/addons/dsrpt_repair_materials/models/material.py @@ -0,0 +1,13 @@ +from odoo import fields, models + + +class RepairMaterial(models.Model): + _name = "repair.material" + _description = "Repair Material" + _order = "name" + + name = fields.Char(required=True) + default_code = fields.Char(string="Code") + uom_name = fields.Char(string="UoM", default="pcs") + standard_cost = fields.Float(string="Cost") + active = fields.Boolean(default=True) diff --git a/odoo/addons/dsrpt_repair_materials/security/ir.model.access.csv b/odoo/addons/dsrpt_repair_materials/security/ir.model.access.csv new file mode 100644 index 0000000..b72d1aa --- /dev/null +++ b/odoo/addons/dsrpt_repair_materials/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_repair_material_user,repair.material user,model_repair_material,base.group_user,1,1,1,1 diff --git a/odoo/addons/dsrpt_repair_materials/views/material_views.xml b/odoo/addons/dsrpt_repair_materials/views/material_views.xml new file mode 100644 index 0000000..f65718c --- /dev/null +++ b/odoo/addons/dsrpt_repair_materials/views/material_views.xml @@ -0,0 +1,42 @@ + + + + repair.material.tree + repair.material + + + + + + + + + + + + + repair.material.form + repair.material + +
+ + + + + + + + + +
+
+
+ + + Materials + repair.material + list,form + + + +
diff --git a/odoo/addons/dsrpt_repair_technicians/__init__.py b/odoo/addons/dsrpt_repair_technicians/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/odoo/addons/dsrpt_repair_technicians/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo/addons/dsrpt_repair_technicians/__manifest__.py b/odoo/addons/dsrpt_repair_technicians/__manifest__.py new file mode 100644 index 0000000..7461494 --- /dev/null +++ b/odoo/addons/dsrpt_repair_technicians/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "DSRPT Repair Technicians", + "summary": "Technicians and availability", + "version": "19.0.1.0.0", + "category": "Services", + "author": "DisruptLab", + "license": "LGPL-3", + "depends": ["base", "dsrpt_repair_main"], + "data": [ + "security/ir.model.access.csv", + "views/technician_views.xml", + ], + "installable": True, +} diff --git a/odoo/addons/dsrpt_repair_technicians/models/__init__.py b/odoo/addons/dsrpt_repair_technicians/models/__init__.py new file mode 100644 index 0000000..0061593 --- /dev/null +++ b/odoo/addons/dsrpt_repair_technicians/models/__init__.py @@ -0,0 +1 @@ +from . import technician diff --git a/odoo/addons/dsrpt_repair_technicians/models/technician.py b/odoo/addons/dsrpt_repair_technicians/models/technician.py new file mode 100644 index 0000000..de4a1ac --- /dev/null +++ b/odoo/addons/dsrpt_repair_technicians/models/technician.py @@ -0,0 +1,66 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class RepairTechnician(models.Model): + _name = "repair.technician" + _description = "Repair Technician" + _order = "name" + + name = fields.Char(required=True) + user_id = fields.Many2one("res.users", string="User") + zone_ids = fields.Many2many("repair.fsm.zone", string="FSM Zones") + active = fields.Boolean(default=True) + schedule_ids = fields.One2many("repair.technician.schedule", "technician_id", string="Weekly Schedule") + exception_ids = fields.One2many("repair.technician.exception", "technician_id", string="Exceptions") + + +class RepairTechnicianSchedule(models.Model): + _name = "repair.technician.schedule" + _description = "Repair Technician Weekly Schedule" + _order = "technician_id, day_of_week, hour_from" + + technician_id = fields.Many2one("repair.technician", required=True, ondelete="cascade") + day_of_week = fields.Selection( + selection=[ + ("0", "Monday"), + ("1", "Tuesday"), + ("2", "Wednesday"), + ("3", "Thursday"), + ("4", "Friday"), + ("5", "Saturday"), + ("6", "Sunday"), + ], + required=True, + ) + hour_from = fields.Float(required=True) + hour_to = fields.Float(required=True) + + @api.constrains("hour_from", "hour_to") + def _check_hours(self): + for rec in self: + if rec.hour_from < 0 or rec.hour_to > 24 or rec.hour_to <= rec.hour_from: + raise ValidationError("Schedule hours must be between 0 and 24, and hour_to must be greater than hour_from.") + + +class RepairTechnicianException(models.Model): + _name = "repair.technician.exception" + _description = "Repair Technician Exception" + _order = "start_datetime desc" + + technician_id = fields.Many2one("repair.technician", required=True, ondelete="cascade") + name = fields.Char(required=True) + exception_type = fields.Selection( + selection=[("positive", "Positive"), ("negative", "Negative")], + required=True, + default="negative", + ) + start_datetime = fields.Datetime(required=True) + end_datetime = fields.Datetime(required=True) + note = fields.Text() + + @api.constrains("start_datetime", "end_datetime") + def _check_dates(self): + for rec in self: + if rec.end_datetime <= rec.start_datetime: + raise ValidationError("Exception end must be after start.") diff --git a/odoo/addons/dsrpt_repair_technicians/security/ir.model.access.csv b/odoo/addons/dsrpt_repair_technicians/security/ir.model.access.csv new file mode 100644 index 0000000..d07d62e --- /dev/null +++ b/odoo/addons/dsrpt_repair_technicians/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_repair_technician_user,repair.technician user,model_repair_technician,base.group_user,1,1,1,1 +access_repair_technician_schedule_user,repair.technician.schedule user,model_repair_technician_schedule,base.group_user,1,1,1,1 +access_repair_technician_exception_user,repair.technician.exception user,model_repair_technician_exception,base.group_user,1,1,1,1 diff --git a/odoo/addons/dsrpt_repair_technicians/views/technician_views.xml b/odoo/addons/dsrpt_repair_technicians/views/technician_views.xml new file mode 100644 index 0000000..9bc5f6f --- /dev/null +++ b/odoo/addons/dsrpt_repair_technicians/views/technician_views.xml @@ -0,0 +1,62 @@ + + + + repair.technician.tree + repair.technician + + + + + + + + + + + + repair.technician.form + repair.technician + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + Technicians + repair.technician + list,form + + + +
diff --git a/odoo/addons/dsrpt_repair_work_orders/__init__.py b/odoo/addons/dsrpt_repair_work_orders/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/odoo/addons/dsrpt_repair_work_orders/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo/addons/dsrpt_repair_work_orders/__manifest__.py b/odoo/addons/dsrpt_repair_work_orders/__manifest__.py new file mode 100644 index 0000000..150ec2f --- /dev/null +++ b/odoo/addons/dsrpt_repair_work_orders/__manifest__.py @@ -0,0 +1,22 @@ +{ + "name": "DSRPT Repair Work Orders", + "summary": "Work order flow from intake to close", + "version": "19.0.1.0.0", + "category": "Services", + "author": "DisruptLab", + "license": "LGPL-3", + "depends": [ + "base", + "mail", + "dsrpt_repair_main", + "dsrpt_repair_customers", + "dsrpt_repair_technicians", + "dsrpt_repair_materials", + ], + "data": [ + "security/ir.model.access.csv", + "data/sequence.xml", + "views/work_order_views.xml", + ], + "installable": True, +} diff --git a/odoo/addons/dsrpt_repair_work_orders/data/sequence.xml b/odoo/addons/dsrpt_repair_work_orders/data/sequence.xml new file mode 100644 index 0000000..211187f --- /dev/null +++ b/odoo/addons/dsrpt_repair_work_orders/data/sequence.xml @@ -0,0 +1,9 @@ + + + + Repair Work Order + repair.work.order + WO%(y)s%(month)s- + 5 + + diff --git a/odoo/addons/dsrpt_repair_work_orders/models/__init__.py b/odoo/addons/dsrpt_repair_work_orders/models/__init__.py new file mode 100644 index 0000000..7e5d214 --- /dev/null +++ b/odoo/addons/dsrpt_repair_work_orders/models/__init__.py @@ -0,0 +1 @@ +from . import work_order diff --git a/odoo/addons/dsrpt_repair_work_orders/models/work_order.py b/odoo/addons/dsrpt_repair_work_orders/models/work_order.py new file mode 100644 index 0000000..9cb2ee7 --- /dev/null +++ b/odoo/addons/dsrpt_repair_work_orders/models/work_order.py @@ -0,0 +1,123 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class RepairWorkOrder(models.Model): + _name = "repair.work.order" + _description = "Repair Work Order" + _inherit = ["mail.thread"] + _order = "id desc" + + name = fields.Char(default="New", copy=False, readonly=True, tracking=True) + customer_id = fields.Many2one("repair.customer", required=True, tracking=True) + customer_contact_id = fields.Many2one( + "repair.customer.contact", + domain="[('customer_id', '=', customer_id)]", + tracking=True, + ) + address_id = fields.Many2one( + "repair.customer.address", + domain="[('customer_id', '=', customer_id)]", + string="Service Address", + tracking=True, + ) + zone_id = fields.Many2one("repair.fsm.zone", string="FSM Zone", tracking=True) + description = fields.Text() + requested_datetime = fields.Datetime(default=fields.Datetime.now) + scheduled_datetime = fields.Datetime(tracking=True) + technician_id = fields.Many2one("repair.technician", tracking=True) + assigned_user_id = fields.Many2one("res.users", related="technician_id.user_id", store=True) + state = fields.Selection( + selection=[ + ("draft", "Draft"), + ("confirmed", "Confirmed"), + ("assigned", "Assigned"), + ("in_progress", "In Progress"), + ("done", "Done"), + ("cancelled", "Cancelled"), + ], + default="draft", + tracking=True, + ) + time_line_ids = fields.One2many("repair.work.order.time", "work_order_id", string="Time Logs") + material_line_ids = fields.One2many("repair.work.order.material", "work_order_id", string="Material Logs") + total_time_hours = fields.Float(compute="_compute_totals", store=True) + total_material_cost = fields.Float(compute="_compute_totals", store=True) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get("name", "New") == "New": + vals["name"] = self.env["ir.sequence"].next_by_code("repair.work.order") or "New" + return super().create(vals_list) + + @api.depends("time_line_ids.hours", "material_line_ids.subtotal") + def _compute_totals(self): + for rec in self: + rec.total_time_hours = sum(rec.time_line_ids.mapped("hours")) + rec.total_material_cost = sum(rec.material_line_ids.mapped("subtotal")) + + @api.onchange("address_id") + def _onchange_address_id(self): + for rec in self: + if rec.address_id and rec.address_id.zone_id: + rec.zone_id = rec.address_id.zone_id + + def action_confirm(self): + self.write({"state": "confirmed"}) + + def action_assign_to_me(self): + technician = self.env["repair.technician"].search([("user_id", "=", self.env.user.id)], limit=1) + if not technician: + raise ValidationError("No technician profile is linked to your user.") + self.write({"technician_id": technician.id, "state": "assigned"}) + + def action_start(self): + self.write({"state": "in_progress"}) + + def action_done(self): + self.write({"state": "done"}) + + def action_cancel(self): + self.write({"state": "cancelled"}) + + def action_reset_draft(self): + self.write({"state": "draft"}) + + +class RepairWorkOrderTime(models.Model): + _name = "repair.work.order.time" + _description = "Repair Work Order Time" + + work_order_id = fields.Many2one("repair.work.order", required=True, ondelete="cascade") + technician_id = fields.Many2one("repair.technician") + description = fields.Char(required=True) + hours = fields.Float(required=True) + + @api.constrains("hours") + def _check_hours(self): + for rec in self: + if rec.hours <= 0: + raise ValidationError("Hours must be greater than zero.") + + +class RepairWorkOrderMaterial(models.Model): + _name = "repair.work.order.material" + _description = "Repair Work Order Material" + + work_order_id = fields.Many2one("repair.work.order", required=True, ondelete="cascade") + material_id = fields.Many2one("repair.material", required=True) + qty = fields.Float(default=1.0, required=True) + unit_cost = fields.Float(related="material_id.standard_cost", store=True, readonly=True) + subtotal = fields.Float(compute="_compute_subtotal", store=True) + + @api.depends("qty", "unit_cost") + def _compute_subtotal(self): + for rec in self: + rec.subtotal = rec.qty * rec.unit_cost + + @api.constrains("qty") + def _check_qty(self): + for rec in self: + if rec.qty <= 0: + raise ValidationError("Material quantity must be greater than zero.") diff --git a/odoo/addons/dsrpt_repair_work_orders/security/ir.model.access.csv b/odoo/addons/dsrpt_repair_work_orders/security/ir.model.access.csv new file mode 100644 index 0000000..b5c5064 --- /dev/null +++ b/odoo/addons/dsrpt_repair_work_orders/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_repair_work_order_user,repair.work.order user,model_repair_work_order,base.group_user,1,1,1,1 +access_repair_work_order_time_user,repair.work.order.time user,model_repair_work_order_time,base.group_user,1,1,1,1 +access_repair_work_order_material_user,repair.work.order.material user,model_repair_work_order_material,base.group_user,1,1,1,1 diff --git a/odoo/addons/dsrpt_repair_work_orders/views/work_order_views.xml b/odoo/addons/dsrpt_repair_work_orders/views/work_order_views.xml new file mode 100644 index 0000000..ade957a --- /dev/null +++ b/odoo/addons/dsrpt_repair_work_orders/views/work_order_views.xml @@ -0,0 +1,92 @@ + + + + repair.work.order.tree + repair.work.order + + + + + + + + + + + + + + + + repair.work.order.form + repair.work.order + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + Work Orders + repair.work.order + list,form + + + +