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
+
+
+
+