Add initial Odoo FSM modules and deployment make targets
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.DS_Store
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
117
Makefile
Normal file
117
Makefile
Normal file
@@ -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
|
||||||
@@ -84,6 +84,13 @@ RUN mkdir -p /var/lib/odoo /mnt/extra-addons \
|
|||||||
&& chown -R odoo:odoo /var/lib/odoo /mnt/extra-addons \
|
&& chown -R odoo:odoo /var/lib/odoo /mnt/extra-addons \
|
||||||
&& chmod -R u+w /var/lib/odoo
|
&& 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)
|
# Build self-contained entrypoint in image (no external files required)
|
||||||
RUN printf '%s\n' \
|
RUN printf '%s\n' \
|
||||||
'#!/usr/bin/env bash' \
|
'#!/usr/bin/env bash' \
|
||||||
|
|||||||
1
odoo/addons/dsrpt_repair_customers/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_customers/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
14
odoo/addons/dsrpt_repair_customers/__manifest__.py
Normal file
14
odoo/addons/dsrpt_repair_customers/__manifest__.py
Normal file
@@ -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,
|
||||||
|
}
|
||||||
1
odoo/addons/dsrpt_repair_customers/models/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_customers/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import customer
|
||||||
42
odoo/addons/dsrpt_repair_customers/models/customer.py
Normal file
42
odoo/addons/dsrpt_repair_customers/models/customer.py
Normal file
@@ -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()
|
||||||
@@ -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
|
||||||
|
57
odoo/addons/dsrpt_repair_customers/views/customer_views.xml
Normal file
57
odoo/addons/dsrpt_repair_customers/views/customer_views.xml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_repair_customer_tree" model="ir.ui.view">
|
||||||
|
<field name="name">repair.customer.tree</field>
|
||||||
|
<field name="model">repair.customer</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="zone_id"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_repair_customer_form" model="ir.ui.view">
|
||||||
|
<field name="name">repair.customer.form</field>
|
||||||
|
<field name="model">repair.customer</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="zone_id"/>
|
||||||
|
<field name="note"/>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Contacts">
|
||||||
|
<field name="contact_ids" context="{'default_customer_id': id}">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="contact_type"/>
|
||||||
|
<field name="value"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Addresses">
|
||||||
|
<field name="address_ids" context="{'default_customer_id': id}">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="label"/>
|
||||||
|
<field name="street"/>
|
||||||
|
<field name="zone_id"/>
|
||||||
|
<field name="details"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_repair_customer" model="ir.actions.act_window">
|
||||||
|
<field name="name">Customers</field>
|
||||||
|
<field name="res_model">repair.customer</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_repair_customers" name="Customers" parent="dsrpt_repair_main.menu_repair_root" action="action_repair_customer" sequence="20"/>
|
||||||
|
</odoo>
|
||||||
1
odoo/addons/dsrpt_repair_main/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_main/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
16
odoo/addons/dsrpt_repair_main/__manifest__.py
Normal file
16
odoo/addons/dsrpt_repair_main/__manifest__.py
Normal file
@@ -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,
|
||||||
|
}
|
||||||
1
odoo/addons/dsrpt_repair_main/models/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_main/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import fsm_zone
|
||||||
11
odoo/addons/dsrpt_repair_main/models/fsm_zone.py
Normal file
11
odoo/addons/dsrpt_repair_main/models/fsm_zone.py
Normal file
@@ -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)
|
||||||
@@ -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
|
||||||
|
38
odoo/addons/dsrpt_repair_main/views/fsm_zone_views.xml
Normal file
38
odoo/addons/dsrpt_repair_main/views/fsm_zone_views.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_repair_fsm_zone_tree" model="ir.ui.view">
|
||||||
|
<field name="name">repair.fsm.zone.tree</field>
|
||||||
|
<field name="model">repair.fsm.zone</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="code"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_repair_fsm_zone_form" model="ir.ui.view">
|
||||||
|
<field name="name">repair.fsm.zone.form</field>
|
||||||
|
<field name="model">repair.fsm.zone</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="code"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_repair_fsm_zone" model="ir.actions.act_window">
|
||||||
|
<field name="name">FSM Zones</field>
|
||||||
|
<field name="res_model">repair.fsm.zone</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_repair_fsm_zone" name="FSM Zones" parent="dsrpt_repair_main.menu_repair_configuration" action="action_repair_fsm_zone" sequence="10"/>
|
||||||
|
</odoo>
|
||||||
5
odoo/addons/dsrpt_repair_main/views/menu.xml
Normal file
5
odoo/addons/dsrpt_repair_main/views/menu.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<menuitem id="menu_repair_root" name="Repair FSM" sequence="10"/>
|
||||||
|
<menuitem id="menu_repair_configuration" name="Configuration" parent="menu_repair_root" sequence="100"/>
|
||||||
|
</odoo>
|
||||||
1
odoo/addons/dsrpt_repair_materials/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_materials/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
14
odoo/addons/dsrpt_repair_materials/__manifest__.py
Normal file
14
odoo/addons/dsrpt_repair_materials/__manifest__.py
Normal file
@@ -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,
|
||||||
|
}
|
||||||
1
odoo/addons/dsrpt_repair_materials/models/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_materials/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import material
|
||||||
13
odoo/addons/dsrpt_repair_materials/models/material.py
Normal file
13
odoo/addons/dsrpt_repair_materials/models/material.py
Normal file
@@ -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)
|
||||||
@@ -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
|
||||||
|
42
odoo/addons/dsrpt_repair_materials/views/material_views.xml
Normal file
42
odoo/addons/dsrpt_repair_materials/views/material_views.xml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_repair_material_tree" model="ir.ui.view">
|
||||||
|
<field name="name">repair.material.tree</field>
|
||||||
|
<field name="model">repair.material</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="default_code"/>
|
||||||
|
<field name="uom_name"/>
|
||||||
|
<field name="standard_cost"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_repair_material_form" model="ir.ui.view">
|
||||||
|
<field name="name">repair.material.form</field>
|
||||||
|
<field name="model">repair.material</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="default_code"/>
|
||||||
|
<field name="uom_name"/>
|
||||||
|
<field name="standard_cost"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_repair_material" model="ir.actions.act_window">
|
||||||
|
<field name="name">Materials</field>
|
||||||
|
<field name="res_model">repair.material</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_repair_materials" name="Materials" parent="dsrpt_repair_main.menu_repair_root" action="action_repair_material" sequence="40"/>
|
||||||
|
</odoo>
|
||||||
1
odoo/addons/dsrpt_repair_technicians/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_technicians/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
14
odoo/addons/dsrpt_repair_technicians/__manifest__.py
Normal file
14
odoo/addons/dsrpt_repair_technicians/__manifest__.py
Normal file
@@ -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,
|
||||||
|
}
|
||||||
1
odoo/addons/dsrpt_repair_technicians/models/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_technicians/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import technician
|
||||||
66
odoo/addons/dsrpt_repair_technicians/models/technician.py
Normal file
66
odoo/addons/dsrpt_repair_technicians/models/technician.py
Normal file
@@ -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.")
|
||||||
@@ -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
|
||||||
|
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_repair_technician_tree" model="ir.ui.view">
|
||||||
|
<field name="name">repair.technician.tree</field>
|
||||||
|
<field name="model">repair.technician</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="zone_ids" widget="many2many_tags"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_repair_technician_form" model="ir.ui.view">
|
||||||
|
<field name="name">repair.technician.form</field>
|
||||||
|
<field name="model">repair.technician</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="zone_ids" widget="many2many_tags"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Weekly Schedule">
|
||||||
|
<field name="schedule_ids" context="{'default_technician_id': id}">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="day_of_week"/>
|
||||||
|
<field name="hour_from"/>
|
||||||
|
<field name="hour_to"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Exceptions">
|
||||||
|
<field name="exception_ids" context="{'default_technician_id': id}">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="exception_type"/>
|
||||||
|
<field name="start_datetime"/>
|
||||||
|
<field name="end_datetime"/>
|
||||||
|
<field name="note"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_repair_technician" model="ir.actions.act_window">
|
||||||
|
<field name="name">Technicians</field>
|
||||||
|
<field name="res_model">repair.technician</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_repair_technicians" name="Technicians" parent="dsrpt_repair_main.menu_repair_root" action="action_repair_technician" sequence="30"/>
|
||||||
|
</odoo>
|
||||||
1
odoo/addons/dsrpt_repair_work_orders/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_work_orders/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
22
odoo/addons/dsrpt_repair_work_orders/__manifest__.py
Normal file
22
odoo/addons/dsrpt_repair_work_orders/__manifest__.py
Normal file
@@ -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,
|
||||||
|
}
|
||||||
9
odoo/addons/dsrpt_repair_work_orders/data/sequence.xml
Normal file
9
odoo/addons/dsrpt_repair_work_orders/data/sequence.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo noupdate="1">
|
||||||
|
<record id="seq_repair_work_order" model="ir.sequence">
|
||||||
|
<field name="name">Repair Work Order</field>
|
||||||
|
<field name="code">repair.work.order</field>
|
||||||
|
<field name="prefix">WO%(y)s%(month)s-</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
1
odoo/addons/dsrpt_repair_work_orders/models/__init__.py
Normal file
1
odoo/addons/dsrpt_repair_work_orders/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import work_order
|
||||||
123
odoo/addons/dsrpt_repair_work_orders/models/work_order.py
Normal file
123
odoo/addons/dsrpt_repair_work_orders/models/work_order.py
Normal file
@@ -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.")
|
||||||
@@ -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
|
||||||
|
@@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_repair_work_order_tree" model="ir.ui.view">
|
||||||
|
<field name="name">repair.work.order.tree</field>
|
||||||
|
<field name="model">repair.work.order</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="customer_id"/>
|
||||||
|
<field name="zone_id"/>
|
||||||
|
<field name="scheduled_datetime"/>
|
||||||
|
<field name="technician_id"/>
|
||||||
|
<field name="state"/>
|
||||||
|
<field name="total_time_hours"/>
|
||||||
|
<field name="total_material_cost"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_repair_work_order_form" model="ir.ui.view">
|
||||||
|
<field name="name">repair.work.order.form</field>
|
||||||
|
<field name="model">repair.work.order</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<header>
|
||||||
|
<button name="action_confirm" type="object" string="Confirm" invisible="state != 'draft'" class="btn-primary"/>
|
||||||
|
<button name="action_assign_to_me" type="object" string="Assign to me" invisible="state not in ('confirmed','assigned')" class="btn-primary"/>
|
||||||
|
<button name="action_start" type="object" string="Start" invisible="state != 'assigned'"/>
|
||||||
|
<button name="action_done" type="object" string="Done" invisible="state not in ('in_progress','assigned')" class="btn-primary"/>
|
||||||
|
<button name="action_cancel" type="object" string="Cancel" invisible="state in ('done','cancelled')"/>
|
||||||
|
<button name="action_reset_draft" type="object" string="Reset to Draft" invisible="state == 'draft'"/>
|
||||||
|
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,assigned,in_progress,done,cancelled"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="name" readonly="1"/>
|
||||||
|
<field name="customer_id"/>
|
||||||
|
<field name="customer_contact_id"/>
|
||||||
|
<field name="address_id"/>
|
||||||
|
<field name="zone_id"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="requested_datetime"/>
|
||||||
|
<field name="scheduled_datetime"/>
|
||||||
|
<field name="technician_id"/>
|
||||||
|
<field name="assigned_user_id" readonly="1"/>
|
||||||
|
<field name="description"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<notebook>
|
||||||
|
<page string="Time Logs">
|
||||||
|
<field name="time_line_ids" context="{'default_work_order_id': id, 'default_technician_id': technician_id}">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="description"/>
|
||||||
|
<field name="technician_id"/>
|
||||||
|
<field name="hours"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Material Logs">
|
||||||
|
<field name="material_line_ids" context="{'default_work_order_id': id}">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="material_id"/>
|
||||||
|
<field name="qty"/>
|
||||||
|
<field name="unit_cost" readonly="1"/>
|
||||||
|
<field name="subtotal" readonly="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Totals">
|
||||||
|
<group>
|
||||||
|
<field name="total_time_hours" readonly="1"/>
|
||||||
|
<field name="total_material_cost" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_repair_work_order" model="ir.actions.act_window">
|
||||||
|
<field name="name">Work Orders</field>
|
||||||
|
<field name="res_model">repair.work.order</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_repair_work_orders" name="Work Orders" parent="dsrpt_repair_main.menu_repair_root" action="action_repair_work_order" sequence="10"/>
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user