chore: rebuild Service Factory with Engagement Card DocType and Kanban workspace
This commit is contained in:
parent
746fbf8d1c
commit
5102e6d4bb
64 changed files with 1697 additions and 3468 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# Service Factory — ERPNext Custom App Project
|
# Service Factory — ERPNext Custom App Project
|
||||||
|
|
||||||
> **Current Version: V0.1.0002** — Each completed prompt/step increments the patch number by 1 (V0.1.0000 → V0.1.0001 → V0.1.0002…). The agent automatically commits and pushes after every step.
|
> **Current Version: V0.1.0003** — Each completed prompt/step increments the patch number by 1 (V0.1.0000 → V0.1.0001 → V0.1.0002…). The agent automatically commits and pushes after every step.
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
|
|
|
||||||
21
service_factory/.editorconfig
Normal file
21
service_factory/.editorconfig
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Root editor config file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Common settings
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# python, js indentation settings
|
||||||
|
[{*.py,*.js,*.vue,*.css,*.scss,*.html}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 99
|
||||||
|
|
||||||
|
# JSON files - mostly doctype schema files
|
||||||
|
[{*.json}]
|
||||||
|
insert_final_newline = false
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 1
|
||||||
124
service_factory/.eslintrc
Normal file
124
service_factory/.eslintrc
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"es2022": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"rules": {
|
||||||
|
"indent": "off",
|
||||||
|
"brace-style": "off",
|
||||||
|
"no-mixed-spaces-and-tabs": "off",
|
||||||
|
"no-useless-escape": "off",
|
||||||
|
"space-unary-ops": ["error", { "words": true }],
|
||||||
|
"linebreak-style": "off",
|
||||||
|
"quotes": ["off"],
|
||||||
|
"semi": "off",
|
||||||
|
"camelcase": "off",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"no-console": ["warn"],
|
||||||
|
"no-extra-boolean-cast": ["off"],
|
||||||
|
"no-control-regex": ["off"],
|
||||||
|
},
|
||||||
|
"root": true,
|
||||||
|
"globals": {
|
||||||
|
"frappe": true,
|
||||||
|
"Vue": true,
|
||||||
|
"SetVueGlobals": true,
|
||||||
|
"__": true,
|
||||||
|
"repl": true,
|
||||||
|
"Class": true,
|
||||||
|
"locals": true,
|
||||||
|
"cint": true,
|
||||||
|
"cstr": true,
|
||||||
|
"cur_frm": true,
|
||||||
|
"cur_dialog": true,
|
||||||
|
"cur_page": true,
|
||||||
|
"cur_list": true,
|
||||||
|
"cur_tree": true,
|
||||||
|
"msg_dialog": true,
|
||||||
|
"is_null": true,
|
||||||
|
"in_list": true,
|
||||||
|
"has_common": true,
|
||||||
|
"has_words": true,
|
||||||
|
"validate_email": true,
|
||||||
|
"open_web_template_values_editor": true,
|
||||||
|
"validate_name": true,
|
||||||
|
"validate_phone": true,
|
||||||
|
"validate_url": true,
|
||||||
|
"get_number_format": true,
|
||||||
|
"format_number": true,
|
||||||
|
"format_currency": true,
|
||||||
|
"comment_when": true,
|
||||||
|
"open_url_post": true,
|
||||||
|
"toTitle": true,
|
||||||
|
"lstrip": true,
|
||||||
|
"rstrip": true,
|
||||||
|
"strip": true,
|
||||||
|
"strip_html": true,
|
||||||
|
"replace_all": true,
|
||||||
|
"flt": true,
|
||||||
|
"precision": true,
|
||||||
|
"CREATE": true,
|
||||||
|
"AMEND": true,
|
||||||
|
"CANCEL": true,
|
||||||
|
"copy_dict": true,
|
||||||
|
"get_number_format_info": true,
|
||||||
|
"strip_number_groups": true,
|
||||||
|
"print_table": true,
|
||||||
|
"Layout": true,
|
||||||
|
"web_form_settings": true,
|
||||||
|
"$c": true,
|
||||||
|
"$a": true,
|
||||||
|
"$i": true,
|
||||||
|
"$bg": true,
|
||||||
|
"$y": true,
|
||||||
|
"$c_obj": true,
|
||||||
|
"refresh_many": true,
|
||||||
|
"refresh_field": true,
|
||||||
|
"toggle_field": true,
|
||||||
|
"get_field_obj": true,
|
||||||
|
"get_query_params": true,
|
||||||
|
"unhide_field": true,
|
||||||
|
"hide_field": true,
|
||||||
|
"set_field_options": true,
|
||||||
|
"getCookie": true,
|
||||||
|
"getCookies": true,
|
||||||
|
"get_url_arg": true,
|
||||||
|
"md5": true,
|
||||||
|
"$": true,
|
||||||
|
"jQuery": true,
|
||||||
|
"moment": true,
|
||||||
|
"hljs": true,
|
||||||
|
"Awesomplete": true,
|
||||||
|
"Sortable": true,
|
||||||
|
"gemoji": true,
|
||||||
|
"Showdown": true,
|
||||||
|
"Taggle": true,
|
||||||
|
"Gantt": true,
|
||||||
|
"Slick": true,
|
||||||
|
"Webcam": true,
|
||||||
|
"PhotoSwipe": true,
|
||||||
|
"PhotoSwipeUI_Default": true,
|
||||||
|
"io": true,
|
||||||
|
"JsBarcode": true,
|
||||||
|
"L": true,
|
||||||
|
"Chart": true,
|
||||||
|
"DataTable": true,
|
||||||
|
"Cypress": true,
|
||||||
|
"cy": true,
|
||||||
|
"it": true,
|
||||||
|
"describe": true,
|
||||||
|
"expect": true,
|
||||||
|
"context": true,
|
||||||
|
"before": true,
|
||||||
|
"beforeEach": true,
|
||||||
|
"after": true,
|
||||||
|
"qz": true,
|
||||||
|
"localforage": true,
|
||||||
|
"extend_cscript": true
|
||||||
|
}
|
||||||
|
}
|
||||||
55
service_factory/.gitignore
vendored
Normal file
55
service_factory/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.pyc
|
||||||
|
*.py~
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
tags
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.vscode/
|
||||||
|
.vs/
|
||||||
|
.idea/
|
||||||
|
.kdev4/
|
||||||
|
*.kdev4
|
||||||
|
*.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.comp.js
|
||||||
|
.wnf-lang-status
|
||||||
|
*debug.log
|
||||||
|
|
||||||
|
# Helix Editor
|
||||||
|
.helix/
|
||||||
|
|
||||||
|
# Aider AI Chat
|
||||||
|
.aider*
|
||||||
69
service_factory/.pre-commit-config.yaml
Normal file
69
service_factory/.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
exclude: 'node_modules|.git'
|
||||||
|
default_stages: [pre-commit]
|
||||||
|
fail_fast: false
|
||||||
|
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v6.0.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
files: "service_factory.*"
|
||||||
|
exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: check-ast
|
||||||
|
- id: check-json
|
||||||
|
- id: check-toml
|
||||||
|
- id: check-yaml
|
||||||
|
- id: debug-statements
|
||||||
|
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.14.10
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
name: "Run ruff import sorter"
|
||||||
|
args: ["--select=I", "--fix"]
|
||||||
|
|
||||||
|
- id: ruff
|
||||||
|
name: "Run ruff linter"
|
||||||
|
|
||||||
|
- id: ruff-format
|
||||||
|
name: "Run ruff formatter"
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: v2.7.1
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
types_or: [javascript, vue, scss]
|
||||||
|
# Ignore any files that might contain jinja / bundles
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
service_factory/public/dist/.*|
|
||||||
|
.*node_modules.*|
|
||||||
|
.*boilerplate.*|
|
||||||
|
service_factory/templates/includes/.*|
|
||||||
|
service_factory/public/js/lib/.*
|
||||||
|
)$
|
||||||
|
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||||
|
rev: v8.44.0
|
||||||
|
hooks:
|
||||||
|
- id: eslint
|
||||||
|
types_or: [javascript]
|
||||||
|
args: ['--quiet']
|
||||||
|
# Ignore any files that might contain jinja / bundles
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
service_factory/public/dist/.*|
|
||||||
|
cypress/.*|
|
||||||
|
.*node_modules.*|
|
||||||
|
.*boilerplate.*|
|
||||||
|
service_factory/templates/includes/.*|
|
||||||
|
service_factory/public/js/lib/.*
|
||||||
|
)$
|
||||||
|
|
||||||
|
ci:
|
||||||
|
autoupdate_schedule: weekly
|
||||||
|
skip: []
|
||||||
|
submodules: false
|
||||||
33
service_factory/README.md
Normal file
33
service_factory/README.md
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
### Service Factory
|
||||||
|
|
||||||
|
Professional Services Delivery & Capacity Management
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
You can install this app using the [bench](https://github.com/frappe/bench) CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd $PATH_TO_YOUR_BENCH
|
||||||
|
bench get-app $URL_OF_THIS_REPO --branch version-16
|
||||||
|
bench install-app service_factory
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/service_factory
|
||||||
|
pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
|
Pre-commit is configured to use the following tools for checking and formatting your code:
|
||||||
|
|
||||||
|
- ruff
|
||||||
|
- eslint
|
||||||
|
- prettier
|
||||||
|
- pyupgrade
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
mit
|
||||||
52
service_factory/deploy/README.md
Normal file
52
service_factory/deploy/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Deployment Configuration
|
||||||
|
|
||||||
|
## Systemd Services
|
||||||
|
|
||||||
|
The Frappe Bench (ERPNext) is managed via systemd for automatic startup on boot.
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
| Service | Description | Port |
|
||||||
|
|---------|-------------|------|
|
||||||
|
| `frappe-bench-web.service` | Web server (gunicorn) | 8000 |
|
||||||
|
| `frappe-bench-worker.service` | Background async worker | - |
|
||||||
|
| `frappe-bench-schedule.service` | Scheduled job runner | - |
|
||||||
|
| `frappe-bench-socketio.service` | Realtime WebSocket server | 9000 |
|
||||||
|
| `frappe-bench-watch.service` | File watcher (dev/assets) | - |
|
||||||
|
|
||||||
|
### Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View status of all services
|
||||||
|
systemctl status frappe-bench-web.service
|
||||||
|
|
||||||
|
# Start/Stop all services
|
||||||
|
systemctl start frappe-bench.target
|
||||||
|
systemctl stop frappe-bench.target
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
journalctl -u frappe-bench-web.service -f
|
||||||
|
journalctl -u frappe-bench-worker.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/frappe-bench-*.service deploy/frappe-bench.target /etc/systemd/system/
|
||||||
|
sudo cp deploy/bench-env.sh /opt/erpnext/frappe-bench/bench-env.sh
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable frappe-bench.target
|
||||||
|
sudo systemctl start frappe-bench.target
|
||||||
|
```
|
||||||
|
|
||||||
|
### Assets Symlink
|
||||||
|
|
||||||
|
After installing the app, create the assets symlink for custom icons:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ln -sf /opt/erpnext/frappe-bench/apps/service_factory/service_factory/public /opt/erpnext/frappe-bench/sites/assets/service_factory
|
||||||
|
```
|
||||||
|
|
||||||
|
### Credentials
|
||||||
|
|
||||||
|
Admin credentials are stored in `/root/erpnext-admin-credentials.txt`.
|
||||||
6
service_factory/deploy/bench-env.sh
Executable file
6
service_factory/deploy/bench-env.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Environment wrapper for Frappe bench services
|
||||||
|
export PATH="/opt/erpnext/home/.local/share/pipx/venvs/frappe-bench/bin:/opt/erpnext/frappe-bench/env/bin:$PATH"
|
||||||
|
export HOME="/opt/erpnext/home"
|
||||||
|
cd /opt/erpnext/frappe-bench
|
||||||
|
exec "$@"
|
||||||
42
service_factory/deploy/frappe-bench
Normal file
42
service_factory/deploy/frappe-bench
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
upstream frappe-web {
|
||||||
|
server 127.0.0.1:8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream frappe-socketio {
|
||||||
|
server 127.0.0.1:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
# Route ALL requests containing /socket.io/ to the SocketIO server
|
||||||
|
# This handles both /socket.io/ and /erp.lohmar.co.uk/socket.io/
|
||||||
|
location ~* /socket\.io/ {
|
||||||
|
rewrite ^(.*)/socket\.io/(.*)$ /socket.io/$2 break;
|
||||||
|
proxy_pass http://frappe-socketio;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 86400;
|
||||||
|
}
|
||||||
|
|
||||||
|
# All other requests go to Frappe web
|
||||||
|
location / {
|
||||||
|
proxy_pass http://frappe-web;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_redirect off;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
service_factory/deploy/frappe-bench-schedule.service
Normal file
19
service_factory/deploy/frappe-bench-schedule.service
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Frappe Bench Scheduler
|
||||||
|
Documentation=https://frappeframework.com
|
||||||
|
After=network.target redis-server.service
|
||||||
|
Requires=redis-server.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=erpnext
|
||||||
|
Group=erpnext
|
||||||
|
WorkingDirectory=/opt/erpnext/frappe-bench
|
||||||
|
ExecStart=/opt/erpnext/frappe-bench/bench-env.sh bench schedule
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=append:/opt/erpnext/frappe-bench/logs/schedule.log
|
||||||
|
StandardError=append:/opt/erpnext/frappe-bench/logs/schedule.error.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
19
service_factory/deploy/frappe-bench-socketio.service
Normal file
19
service_factory/deploy/frappe-bench-socketio.service
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Frappe Bench SocketIO (Realtime)
|
||||||
|
Documentation=https://frappeframework.com
|
||||||
|
After=network.target redis-server.service
|
||||||
|
Requires=redis-server.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=erpnext
|
||||||
|
Group=erpnext
|
||||||
|
WorkingDirectory=/opt/erpnext/frappe-bench
|
||||||
|
ExecStart=/opt/erpnext/frappe-bench/bench-env.sh bench socketio
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=append:/opt/erpnext/frappe-bench/logs/socketio.log
|
||||||
|
StandardError=append:/opt/erpnext/frappe-bench/logs/socketio.error.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
19
service_factory/deploy/frappe-bench-watch.service
Normal file
19
service_factory/deploy/frappe-bench-watch.service
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Frappe Bench File Watcher (Development)
|
||||||
|
Documentation=https://frappeframework.com
|
||||||
|
After=network.target
|
||||||
|
PartOf=frappe-bench-web.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=erpnext
|
||||||
|
Group=erpnext
|
||||||
|
WorkingDirectory=/opt/erpnext/frappe-bench
|
||||||
|
ExecStart=/opt/erpnext/frappe-bench/bench-env.sh bench watch
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=append:/opt/erpnext/frappe-bench/logs/watch.log
|
||||||
|
StandardError=append:/opt/erpnext/frappe-bench/logs/watch.error.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
19
service_factory/deploy/frappe-bench-web.service
Normal file
19
service_factory/deploy/frappe-bench-web.service
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Frappe Bench Web Server (ERPNext)
|
||||||
|
Documentation=https://frappeframework.com
|
||||||
|
After=network.target redis-server.service
|
||||||
|
Requires=redis-server.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=erpnext
|
||||||
|
Group=erpnext
|
||||||
|
WorkingDirectory=/opt/erpnext/frappe-bench
|
||||||
|
ExecStart=/opt/erpnext/frappe-bench/bench-env.sh bench serve --port 8000 --proxy
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=append:/opt/erpnext/frappe-bench/logs/web.log
|
||||||
|
StandardError=append:/opt/erpnext/frappe-bench/logs/web.error.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
19
service_factory/deploy/frappe-bench-worker.service
Normal file
19
service_factory/deploy/frappe-bench-worker.service
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Frappe Bench Background Worker
|
||||||
|
Documentation=https://frappeframework.com
|
||||||
|
After=network.target redis-server.service
|
||||||
|
Requires=redis-server.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=erpnext
|
||||||
|
Group=erpnext
|
||||||
|
WorkingDirectory=/opt/erpnext/frappe-bench
|
||||||
|
ExecStart=/opt/erpnext/frappe-bench/bench-env.sh bench worker
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=append:/opt/erpnext/frappe-bench/logs/worker.log
|
||||||
|
StandardError=append:/opt/erpnext/frappe-bench/logs/worker.error.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
9
service_factory/deploy/frappe-bench.target
Normal file
9
service_factory/deploy/frappe-bench.target
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Frappe Bench (ERPNext) - All Services
|
||||||
|
Documentation=https://frappeframework.com
|
||||||
|
Requires=redis-server.service
|
||||||
|
After=redis-server.service network.target
|
||||||
|
Wants=frappe-bench-web.service frappe-bench-worker.service frappe-bench-schedule.service frappe-bench-socketio.service frappe-bench-watch.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
21
service_factory/license.txt
Normal file
21
service_factory/license.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) [year] [fullname]
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
65
service_factory/pyproject.toml
Normal file
65
service_factory/pyproject.toml
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
[project]
|
||||||
|
name = "service_factory"
|
||||||
|
authors = [
|
||||||
|
{ name = "cclohmar", email = "claus.lohmar@lohmar.co.uk"}
|
||||||
|
]
|
||||||
|
description = "Professional Services Delivery & Capacity Management"
|
||||||
|
requires-python = ">=3.14"
|
||||||
|
readme = "README.md"
|
||||||
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
# "frappe~=16.0.0" # Installed and managed by bench.
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["flit_core >=3.4,<4"]
|
||||||
|
build-backend = "flit_core.buildapi"
|
||||||
|
|
||||||
|
# These dependencies are only installed when developer mode is enabled
|
||||||
|
[tool.bench.dev-dependencies]
|
||||||
|
# package_name = "~=1.1.0"
|
||||||
|
|
||||||
|
# These apt dependencies will be installed from Ubuntu repositories when you host your app on Frappe Cloud
|
||||||
|
[deploy.dependencies.apt]
|
||||||
|
packages = []
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 110
|
||||||
|
target-version = "py314"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"F",
|
||||||
|
"E",
|
||||||
|
"W",
|
||||||
|
"I",
|
||||||
|
"UP",
|
||||||
|
"B",
|
||||||
|
"RUF",
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"B017", # assertRaises(Exception) - should be more specific
|
||||||
|
"B018", # useless expression, not assigned to anything
|
||||||
|
"B023", # function doesn't bind loop variable - will have last iteration's value
|
||||||
|
"B904", # raise inside except without from
|
||||||
|
"E101", # indentation contains mixed spaces and tabs
|
||||||
|
"E402", # module level import not at top of file
|
||||||
|
"E501", # line too long
|
||||||
|
"E741", # ambiguous variable name
|
||||||
|
"F401", # "unused" imports
|
||||||
|
"F403", # can't detect undefined names from * import
|
||||||
|
"F405", # can't detect undefined names from * import
|
||||||
|
"F722", # syntax error in forward type annotation
|
||||||
|
"W191", # indentation contains tabs
|
||||||
|
"UP030", # Use implicit references for positional format fields (translations)
|
||||||
|
"UP031", # Use format specifiers instead of percent format
|
||||||
|
"UP032", # Use f-string instead of `format` call (translations)
|
||||||
|
"UP037", # quoted annotations
|
||||||
|
"UP040", # Use type aliases instead of type annotations
|
||||||
|
]
|
||||||
|
typing-modules = ["frappe.types.DF"]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "tab"
|
||||||
|
docstring-code-format = true
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
__version__ = "0.0.1"
|
||||||
12
service_factory/service_factory/api/permission.py
Normal file
12
service_factory/service_factory/api/permission.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Copyright (c) 2026, cclohmar and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
def has_app_permission():
|
||||||
|
"""Check if the current user has permission to see the Service Factory app icon."""
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
if frappe.session.user == "Guest":
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
@ -1,549 +0,0 @@
|
||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_auto_repeat": 0,
|
|
||||||
"allow_events_in_timeline": 1,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 1,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "naming_series:",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "Document",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"naming_series",
|
|
||||||
"customer",
|
|
||||||
"sales_order",
|
|
||||||
"engagement_recipe",
|
|
||||||
"column_break_1",
|
|
||||||
"status",
|
|
||||||
"start_date",
|
|
||||||
"end_date",
|
|
||||||
"section_break_totals",
|
|
||||||
"total_allocated_hours",
|
|
||||||
"total_logged_hours",
|
|
||||||
"column_break_totals_2",
|
|
||||||
"actual_cost",
|
|
||||||
"projected_billing_value",
|
|
||||||
"section_break_steps",
|
|
||||||
"run_steps"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "naming_series",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Series",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "ENG-ORD-.YYYY.-",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "customer",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Customer",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Customer",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "sales_order",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Sales Order",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Sales Order",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "engagement_recipe",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Engagement Recipe",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Engagement Recipe",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_1",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "status",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Status",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Draft\nScheduled\nIn Progress\nOn Hold\nCompleted\nCancelled",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "start_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Start Date",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "end_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "End Date",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_totals",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Totals",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "total_allocated_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Total Allocated Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "1",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "total_logged_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Total Logged Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "1",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_totals_2",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "actual_cost",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Actual Cost",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "projected_billing_value",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Projected Billing Value",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_steps",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Engagement Steps",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "run_steps",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Run Steps",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Engagement Order Run Step",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_field_mappings": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_field": "",
|
|
||||||
"import_source": "",
|
|
||||||
"is_submittable": 1,
|
|
||||||
"is_table": 0,
|
|
||||||
"label": "Engagement Order",
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Engagement Order",
|
|
||||||
"name_case": "",
|
|
||||||
"nsm_parent_field": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 1,
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"select": 1,
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 1,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
# Copyright (c) 2026, cclohmar and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe import _
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
|
|
||||||
class EngagementOrder(Document):
|
|
||||||
def validate(self):
|
|
||||||
"""Validate before saving."""
|
|
||||||
self.check_recipe_changed()
|
|
||||||
self.calculate_totals()
|
|
||||||
self.capacity_warning()
|
|
||||||
|
|
||||||
def check_recipe_changed(self):
|
|
||||||
"""Detect if engagement_recipe changed and re-populate steps."""
|
|
||||||
if not self.engagement_recipe:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if this is a new doc or recipe changed
|
|
||||||
if self.is_new() or self.has_value_changed("engagement_recipe"):
|
|
||||||
self.load_recipe_steps()
|
|
||||||
|
|
||||||
def load_recipe_steps(self):
|
|
||||||
"""Fetch recipe steps into order run steps."""
|
|
||||||
recipe = frappe.get_doc("Engagement Recipe", self.engagement_recipe)
|
|
||||||
self.set("run_steps", [])
|
|
||||||
|
|
||||||
for step in recipe.steps:
|
|
||||||
row = self.append("run_steps", {})
|
|
||||||
row.step_name = step.step_name
|
|
||||||
row.sequence_no = step.sequence_no
|
|
||||||
row.resource_center = step.resource_center
|
|
||||||
row.allocated_hours = step.estimated_hours
|
|
||||||
row.status = "Pending"
|
|
||||||
|
|
||||||
def calculate_totals(self):
|
|
||||||
"""Recalculate order-level totals from run steps."""
|
|
||||||
total_allocated = 0.0
|
|
||||||
total_logged = 0.0
|
|
||||||
actual_cost = 0.0
|
|
||||||
projected_billing = 0.0
|
|
||||||
|
|
||||||
if self.get("run_steps"):
|
|
||||||
for step in self.run_steps:
|
|
||||||
allocated = step.get("allocated_hours", 0) or 0
|
|
||||||
logged = step.get("actual_hours", 0) or 0
|
|
||||||
total_allocated += allocated
|
|
||||||
total_logged += logged
|
|
||||||
|
|
||||||
if step.resource_center:
|
|
||||||
rc = frappe.get_cached_value(
|
|
||||||
"Resource Center",
|
|
||||||
step.resource_center,
|
|
||||||
["hourly_cost_rate", "hourly_billing_rate"],
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
if rc:
|
|
||||||
actual_cost += logged * (rc.get("hourly_cost_rate", 0) or 0)
|
|
||||||
projected_billing += logged * (rc.get("hourly_billing_rate", 0) or 0)
|
|
||||||
|
|
||||||
self.total_allocated_hours = total_allocated
|
|
||||||
self.total_logged_hours = total_logged
|
|
||||||
self.actual_cost = actual_cost
|
|
||||||
self.projected_billing_value = projected_billing
|
|
||||||
|
|
||||||
def capacity_warning(self):
|
|
||||||
"""Warn if requested hours exceed Resource Center daily capacity."""
|
|
||||||
if not self.get("run_steps") or not self.start_date:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Group steps by resource center and sum hours
|
|
||||||
rc_hours = {}
|
|
||||||
for step in self.run_steps:
|
|
||||||
if not step.resource_center:
|
|
||||||
continue
|
|
||||||
rc = step.resource_center
|
|
||||||
hours = step.get("allocated_hours", 0) or 0
|
|
||||||
rc_hours[rc] = rc_hours.get(rc, 0) + hours
|
|
||||||
|
|
||||||
for rc_name, requested_hours in rc_hours.items():
|
|
||||||
rc_doc = frappe.get_cached_doc("Resource Center", rc_name)
|
|
||||||
daily_capacity = rc_doc.daily_capacity_hours or 0
|
|
||||||
|
|
||||||
if daily_capacity <= 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Find overlapping Engagement Orders for this Resource Center
|
|
||||||
overlapping_hours = self.get_overlapping_hours(rc_name)
|
|
||||||
|
|
||||||
total_daily_load = overlapping_hours + requested_hours
|
|
||||||
|
|
||||||
if total_daily_load > daily_capacity:
|
|
||||||
frappe.msgprint(
|
|
||||||
_(
|
|
||||||
"Warning: Resource Center '{0}' has a daily capacity of {1} hours, "
|
|
||||||
"but the total load including this order would be {2} hours "
|
|
||||||
"({3}% utilization). Consider adjusting dates or hours."
|
|
||||||
).format(
|
|
||||||
rc_name,
|
|
||||||
daily_capacity,
|
|
||||||
total_daily_load,
|
|
||||||
round((total_daily_load / daily_capacity) * 100, 0),
|
|
||||||
),
|
|
||||||
indicator="orange",
|
|
||||||
alert=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_overlapping_hours(self, resource_center):
|
|
||||||
"""Sum allocated hours from other active Engagement Orders
|
|
||||||
that overlap with this order's date range."""
|
|
||||||
if not self.start_date:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
start = self.start_date
|
|
||||||
# If no end date, estimate 1 day
|
|
||||||
end = self.end_date or start
|
|
||||||
|
|
||||||
filters = {
|
|
||||||
"name": ["!=", self.name],
|
|
||||||
"docstatus": 1, # Submitted
|
|
||||||
"start_date": ["<=", end],
|
|
||||||
}
|
|
||||||
# Only add end_date filter if it's set on the order
|
|
||||||
if self.end_date:
|
|
||||||
filters["end_date"] = [">=", start]
|
|
||||||
else:
|
|
||||||
# Orders that started on or before our end date and might still be active
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Also check status
|
|
||||||
statuses = ["Scheduled", "In Progress"]
|
|
||||||
|
|
||||||
orders = frappe.get_all(
|
|
||||||
"Engagement Order",
|
|
||||||
filters=filters,
|
|
||||||
pluck="name",
|
|
||||||
)
|
|
||||||
|
|
||||||
total_hours = 0
|
|
||||||
for order_name in orders:
|
|
||||||
order_doc = frappe.get_cached_doc("Engagement Order", order_name)
|
|
||||||
for step in order_doc.get("run_steps", []):
|
|
||||||
if step.resource_center == resource_center:
|
|
||||||
total_hours += step.get("allocated_hours", 0) or 0
|
|
||||||
|
|
||||||
return total_hours
|
|
||||||
|
|
||||||
def on_submit(self):
|
|
||||||
self.create_execution_cards()
|
|
||||||
|
|
||||||
def create_execution_cards(self):
|
|
||||||
"""Auto-generate Execution Cards for each run step
|
|
||||||
that has an assigned employee."""
|
|
||||||
for step in self.get("run_steps", []):
|
|
||||||
if not step.assigned_employee:
|
|
||||||
continue
|
|
||||||
|
|
||||||
card = frappe.get_doc({
|
|
||||||
"doctype": "Execution Card",
|
|
||||||
"engagement_order": self.name,
|
|
||||||
"step_name": step.step_name,
|
|
||||||
"assigned_employee": step.assigned_employee,
|
|
||||||
"allocated_hours": step.allocated_hours or 0,
|
|
||||||
"status": "Open",
|
|
||||||
})
|
|
||||||
card.insert()
|
|
||||||
step.db_set("status", "Active")
|
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
# Copyright (c) 2026, cclohmar and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.tests.utils import FrappeTestCase
|
|
||||||
from frappe.utils import now_datetime
|
|
||||||
|
|
||||||
|
|
||||||
class TestEngagementOrder(FrappeTestCase):
|
|
||||||
"""Test Suite for Engagement Order core logic.
|
|
||||||
|
|
||||||
Tests:
|
|
||||||
1. Submitting an Engagement Order generates Execution Cards.
|
|
||||||
2. Clocking hours on an Execution Card rolls up costs to parent.
|
|
||||||
3. Capacity warnings fire on overallocation.
|
|
||||||
4. Recipe steps auto-populate via load_recipe_steps().
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Set up minimal test dependencies."""
|
|
||||||
self.resource_center = self._create_resource_center()
|
|
||||||
self.employee = self._create_employee()
|
|
||||||
self.customer = self._create_customer()
|
|
||||||
self.recipe = self._create_recipe()
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Helpers
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _create_resource_center(self):
|
|
||||||
name = "_Test RC"
|
|
||||||
if frappe.db.exists("Resource Center", name):
|
|
||||||
return frappe.get_doc("Resource Center", name)
|
|
||||||
|
|
||||||
rc = frappe.get_doc({
|
|
||||||
"doctype": "Resource Center",
|
|
||||||
"center_name": name,
|
|
||||||
"resource_type": "Engineering",
|
|
||||||
"hourly_cost_rate": 75.0,
|
|
||||||
"hourly_billing_rate": 150.0,
|
|
||||||
"daily_capacity_hours": 40.0,
|
|
||||||
})
|
|
||||||
rc.insert()
|
|
||||||
return rc
|
|
||||||
|
|
||||||
def _create_employee(self):
|
|
||||||
email = "test_engineer@engagementorder.test"
|
|
||||||
if not frappe.db.get_value("User", email):
|
|
||||||
frappe.get_doc({
|
|
||||||
"doctype": "User",
|
|
||||||
"email": email,
|
|
||||||
"first_name": "Test Engineer",
|
|
||||||
"send_welcome_email": 0,
|
|
||||||
"roles": [{"doctype": "Has Role", "role": "Employee"}],
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
if not frappe.db.exists("Employee", {"user_id": email}):
|
|
||||||
emp = frappe.get_doc({
|
|
||||||
"doctype": "Employee",
|
|
||||||
"naming_series": "EMP-",
|
|
||||||
"first_name": email,
|
|
||||||
"last_name": "",
|
|
||||||
"company": frappe.db.get_single_value("Global Defaults", "default_company")
|
|
||||||
or frappe.db.exists("Company", {}) and frappe.get_all("Company")[0].name
|
|
||||||
or "_Test Company",
|
|
||||||
"user_id": email,
|
|
||||||
"date_of_birth": "1990-01-01",
|
|
||||||
"date_of_joining": "2020-01-01",
|
|
||||||
"status": "Active",
|
|
||||||
"employment_type": "Intern",
|
|
||||||
"gender": "Female",
|
|
||||||
"company_email": email,
|
|
||||||
})
|
|
||||||
emp.insert()
|
|
||||||
return emp.name
|
|
||||||
|
|
||||||
return frappe.get_value("Employee", {"user_id": email}, "name")
|
|
||||||
|
|
||||||
def _create_customer(self):
|
|
||||||
name = "_Test EO Customer"
|
|
||||||
if frappe.db.exists("Customer", name):
|
|
||||||
return frappe.get_doc("Customer", name)
|
|
||||||
|
|
||||||
customer = frappe.get_doc({
|
|
||||||
"doctype": "Customer",
|
|
||||||
"customer_name": name,
|
|
||||||
"customer_type": "Company",
|
|
||||||
"customer_group": frappe.db.get_value("Customer Group", {"is_group": 0}, "name")
|
|
||||||
or "All Customer Groups",
|
|
||||||
"territory": frappe.db.get_value("Territory", {"is_group": 0}, "name")
|
|
||||||
or "All Territories",
|
|
||||||
})
|
|
||||||
customer.insert()
|
|
||||||
return customer
|
|
||||||
|
|
||||||
def _create_recipe(self):
|
|
||||||
name = "_Test EO Recipe"
|
|
||||||
if frappe.db.exists("Engagement Recipe", name):
|
|
||||||
return frappe.get_doc("Engagement Recipe", name)
|
|
||||||
|
|
||||||
recipe = frappe.get_doc({
|
|
||||||
"doctype": "Engagement Recipe",
|
|
||||||
"recipe_name": name,
|
|
||||||
"steps": [
|
|
||||||
{"step_name": "Discovery", "sequence_no": 1,
|
|
||||||
"resource_center": self.resource_center.name, "estimated_hours": 4},
|
|
||||||
{"step_name": "Setup", "sequence_no": 2,
|
|
||||||
"resource_center": self.resource_center.name, "estimated_hours": 8,
|
|
||||||
"dependency_step": 1},
|
|
||||||
{"step_name": "Training", "sequence_no": 3,
|
|
||||||
"resource_center": self.resource_center.name, "estimated_hours": 3,
|
|
||||||
"dependency_step": 2},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
recipe.insert()
|
|
||||||
return recipe
|
|
||||||
|
|
||||||
def _make_order(self, **kwargs):
|
|
||||||
"""Create an unsaved Engagement Order with one step."""
|
|
||||||
order = frappe.get_doc({
|
|
||||||
"doctype": "Engagement Order",
|
|
||||||
"customer": kwargs.get("customer", self.customer.name),
|
|
||||||
"engagement_recipe": self.recipe.name,
|
|
||||||
"start_date": kwargs.get("start_date", frappe.utils.today()),
|
|
||||||
"run_steps": [
|
|
||||||
{
|
|
||||||
"step_name": kwargs.get("step_name", "Discovery"),
|
|
||||||
"sequence_no": 1,
|
|
||||||
"resource_center": kwargs.get("rc", self.resource_center.name),
|
|
||||||
"assigned_employee": kwargs.get("employee", self.employee),
|
|
||||||
"allocated_hours": kwargs.get("hours", 10),
|
|
||||||
"status": "Pending",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
return order
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Tests
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
def test_execution_cards_generated_on_submit(self):
|
|
||||||
"""Submitting an Engagement Order creates Execution Cards."""
|
|
||||||
order = self._make_order()
|
|
||||||
order.insert()
|
|
||||||
order.submit()
|
|
||||||
|
|
||||||
cards = frappe.get_all(
|
|
||||||
"Execution Card",
|
|
||||||
filters={"engagement_order": order.name},
|
|
||||||
fields=["name", "step_name", "status"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(len(cards), 1)
|
|
||||||
self.assertEqual(cards[0].step_name, "Discovery")
|
|
||||||
self.assertEqual(cards[0].status, "Open")
|
|
||||||
|
|
||||||
def test_cost_rollup_on_card_update(self):
|
|
||||||
"""Adding 2 hours to an Execution Card rolls up cost/billing.
|
|
||||||
|
|
||||||
Formula:
|
|
||||||
actual_cost = 2h * $75 = $150
|
|
||||||
projected_billing_value = 2h * $150 = $300
|
|
||||||
"""
|
|
||||||
order = self._make_order()
|
|
||||||
order.insert()
|
|
||||||
order.submit()
|
|
||||||
|
|
||||||
card_name = frappe.get_all(
|
|
||||||
"Execution Card",
|
|
||||||
filters={"engagement_order": order.name},
|
|
||||||
pluck="name",
|
|
||||||
)[0]
|
|
||||||
card = frappe.get_doc("Execution Card", card_name)
|
|
||||||
|
|
||||||
start = now_datetime()
|
|
||||||
|
|
||||||
# Simulate 2h clocked via start/pause (add to time_logs)
|
|
||||||
card.append("time_logs", {
|
|
||||||
"start_time": start,
|
|
||||||
"end_time": frappe.utils.add_to_date(start, hours=2),
|
|
||||||
"duration_hours": 2.0,
|
|
||||||
})
|
|
||||||
card.status = "Submitted"
|
|
||||||
card.save()
|
|
||||||
|
|
||||||
order.reload()
|
|
||||||
self.assertEqual(order.total_logged_hours, 2.0)
|
|
||||||
self.assertEqual(order.actual_cost, 150.0)
|
|
||||||
self.assertEqual(order.projected_billing_value, 300.0)
|
|
||||||
self.assertEqual(order.run_steps[0].actual_hours, 2.0)
|
|
||||||
|
|
||||||
def test_recipe_step_auto_populate(self):
|
|
||||||
"""load_recipe_steps() populates run steps from the recipe."""
|
|
||||||
order = self._make_order()
|
|
||||||
order.load_recipe_steps()
|
|
||||||
|
|
||||||
self.assertEqual(len(order.run_steps), 3)
|
|
||||||
self.assertEqual(order.run_steps[0].step_name, "Discovery")
|
|
||||||
self.assertEqual(order.run_steps[1].allocated_hours, 8)
|
|
||||||
self.assertEqual(order.run_steps[2].estimated_hours, 3)
|
|
||||||
|
|
||||||
def test_capacity_warning_fires(self):
|
|
||||||
"""Capacity warning fires when hours exceed Resource Center limit."""
|
|
||||||
rc_name = self.resource_center.name
|
|
||||||
daily_cap = self.resource_center.daily_capacity_hours # 40
|
|
||||||
|
|
||||||
# Fill most of the capacity
|
|
||||||
order1 = self._make_order(hours=daily_cap - 2)
|
|
||||||
order1.insert()
|
|
||||||
order1.submit()
|
|
||||||
|
|
||||||
# Try adding more — capacity_warning runs in validate()
|
|
||||||
order2 = self._make_order(hours=4)
|
|
||||||
|
|
||||||
# Check that a warning message was generated
|
|
||||||
warning_msgs = [
|
|
||||||
m for m in frappe.message_log
|
|
||||||
if "Warning" in str(m.get("message", ""))
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertGreaterEqual(len(warning_msgs), 0) # just verify it runs
|
|
||||||
|
|
@ -1,281 +0,0 @@
|
||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"autoname": "hash",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"step_name",
|
|
||||||
"sequence_no",
|
|
||||||
"resource_center",
|
|
||||||
"assigned_employee",
|
|
||||||
"column_break_1",
|
|
||||||
"allocated_hours",
|
|
||||||
"actual_hours",
|
|
||||||
"status"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "step_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Step Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "sequence_no",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Sequence No",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "resource_center",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Resource Center",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Resource Center",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "assigned_employee",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Assigned Employee",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Employee",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_1",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "allocated_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Allocated Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "1",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "actual_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Actual Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "1",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "status",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Status",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Pending\nActive\nCompleted",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_field_mappings": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_field": "",
|
|
||||||
"import_source": "",
|
|
||||||
"is_submittable": 0,
|
|
||||||
"is_table": 1,
|
|
||||||
"label": "Engagement Order Run Step",
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Engagement Order Run Step",
|
|
||||||
"name_case": "",
|
|
||||||
"nsm_parent_field": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,245 +0,0 @@
|
||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_auto_repeat": 0,
|
|
||||||
"allow_events_in_timeline": 1,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 1,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "field:recipe_name",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "Master",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"recipe_name",
|
|
||||||
"service_offering",
|
|
||||||
"column_break_1",
|
|
||||||
"estimated_total_duration_hours",
|
|
||||||
"section_break_steps",
|
|
||||||
"steps"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 1,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "recipe_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Recipe Name",
|
|
||||||
"length": 140,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 1,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "service_offering",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Service Offering",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Item",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_1",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "estimated_total_duration_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Estimated Total Duration Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "1",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_steps",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Engagement Steps",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "steps",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Steps",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Engagement Recipe Step",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_field_mappings": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_field": "",
|
|
||||||
"import_source": "",
|
|
||||||
"is_submittable": 0,
|
|
||||||
"is_table": 0,
|
|
||||||
"label": "Engagement Recipe",
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Engagement Recipe",
|
|
||||||
"name_case": "",
|
|
||||||
"nsm_parent_field": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"select": 1,
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# Copyright (c) 2026, cclohmar and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class EngagementRecipe(Document):
|
|
||||||
def before_save(self):
|
|
||||||
self.calculate_total_duration()
|
|
||||||
|
|
||||||
def calculate_total_duration(self):
|
|
||||||
total = 0.0
|
|
||||||
if self.get("steps"):
|
|
||||||
for step in self.steps:
|
|
||||||
total += step.get("estimated_hours", 0) or 0
|
|
||||||
self.estimated_total_duration_hours = total
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"autoname": "hash",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"step_name",
|
|
||||||
"sequence_no",
|
|
||||||
"resource_center",
|
|
||||||
"column_break_1",
|
|
||||||
"estimated_hours",
|
|
||||||
"dependency_step"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "step_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Step Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "sequence_no",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Sequence No",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "resource_center",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Resource Center",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Resource Center",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_1",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "estimated_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Estimated Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "1",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "dependency_step",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Dependency Step",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_field_mappings": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_field": "",
|
|
||||||
"import_source": "",
|
|
||||||
"is_submittable": 0,
|
|
||||||
"is_table": 1,
|
|
||||||
"label": "Engagement Recipe Step",
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Engagement Recipe Step",
|
|
||||||
"name_case": "",
|
|
||||||
"nsm_parent_field": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
// Copyright (c) 2026, cclohmar and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on("Execution Card", {
|
|
||||||
refresh: function (frm) {
|
|
||||||
setup_timer_widget(frm);
|
|
||||||
},
|
|
||||||
|
|
||||||
status: function (frm) {
|
|
||||||
setup_timer_widget(frm);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function setup_timer_widget(frm) {
|
|
||||||
// Clear existing timer buttons
|
|
||||||
frm.remove_custom_button(__("Start Work"), "Actions");
|
|
||||||
frm.remove_custom_button(__("Pause Work"), "Actions");
|
|
||||||
frm.remove_custom_button(__("Stop Work"), "Actions");
|
|
||||||
|
|
||||||
// Stop any running timer interval
|
|
||||||
if (frm._timer_interval) {
|
|
||||||
clearInterval(frm._timer_interval);
|
|
||||||
frm._timer_interval = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove existing timer display
|
|
||||||
frm.$wrapper.find(".execution-card-timer").remove();
|
|
||||||
|
|
||||||
const status = frm.doc.status;
|
|
||||||
|
|
||||||
if (status === "Open" || status === "Paused") {
|
|
||||||
frm.add_custom_button(
|
|
||||||
__("Start Work"),
|
|
||||||
function () {
|
|
||||||
frm.call("start_work").then((r) => {
|
|
||||||
frm.refresh();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
__("Actions")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === "Running") {
|
|
||||||
frm.add_custom_button(
|
|
||||||
__("Pause Work"),
|
|
||||||
function () {
|
|
||||||
frm.call("pause_work").then((r) => {
|
|
||||||
frm.refresh();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
__("Actions")
|
|
||||||
);
|
|
||||||
|
|
||||||
frm.add_custom_button(
|
|
||||||
__("Stop Work"),
|
|
||||||
function () {
|
|
||||||
frm.call("stop_work").then((r) => {
|
|
||||||
frm.refresh();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
__("Actions")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start live timer display
|
|
||||||
start_live_timer(frm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === "Submitted") {
|
|
||||||
frm.add_custom_button(
|
|
||||||
__("View Time Logs"),
|
|
||||||
function () {
|
|
||||||
frappe.set_route("List", "Execution Card Log", {
|
|
||||||
parent: frm.doc.name,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
__("Actions")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function start_live_timer(frm) {
|
|
||||||
// Add a timer display element
|
|
||||||
const $timer = $(`
|
|
||||||
<div class="execution-card-timer">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12 text-center" style="padding: 15px 0;">
|
|
||||||
<div style="font-size: 14px; color: #666;">⏱ ${__("Elapsed Time")}</div>
|
|
||||||
<div class="timer-display" style="font-size: 48px; font-weight: bold; font-family: monospace; color: #2490ef;">
|
|
||||||
00:00:00
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Insert timer after the form title
|
|
||||||
frm.$wrapper.find(".form-page").prepend($timer);
|
|
||||||
|
|
||||||
// Poll for elapsed time every second
|
|
||||||
frm._timer_interval = setInterval(function () {
|
|
||||||
frm.call("get_active_time").then((r) => {
|
|
||||||
if (r.message) {
|
|
||||||
const seconds = r.message.elapsed_seconds || 0;
|
|
||||||
const display = format_seconds(seconds);
|
|
||||||
$timer.find(".timer-display").text(display);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function format_seconds(total_seconds) {
|
|
||||||
const hours = Math.floor(total_seconds / 3600);
|
|
||||||
const minutes = Math.floor((total_seconds % 3600) / 60);
|
|
||||||
const seconds = Math.floor(total_seconds % 60);
|
|
||||||
|
|
||||||
return (
|
|
||||||
String(hours).padStart(2, "0") +
|
|
||||||
":" +
|
|
||||||
String(minutes).padStart(2, "0") +
|
|
||||||
":" +
|
|
||||||
String(seconds).padStart(2, "0")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,422 +0,0 @@
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"action_name": "Start Work",
|
|
||||||
"group": "Action",
|
|
||||||
"hidden": 0,
|
|
||||||
"action_type": "Server Action",
|
|
||||||
"action": "start_work"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action_name": "Pause Work",
|
|
||||||
"group": "Action",
|
|
||||||
"hidden": 0,
|
|
||||||
"action_type": "Server Action",
|
|
||||||
"action": "pause_work"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action_name": "Stop Work",
|
|
||||||
"group": "Action",
|
|
||||||
"hidden": 0,
|
|
||||||
"action_type": "Server Action",
|
|
||||||
"action": "stop_work"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"allow_auto_repeat": 0,
|
|
||||||
"allow_events_in_timeline": 1,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "naming_series:",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "Document",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"naming_series",
|
|
||||||
"engagement_order",
|
|
||||||
"step_name",
|
|
||||||
"column_break_1",
|
|
||||||
"assigned_employee",
|
|
||||||
"allocated_hours",
|
|
||||||
"actual_hours_logged",
|
|
||||||
"status",
|
|
||||||
"section_break_timer",
|
|
||||||
"timer_status",
|
|
||||||
"elapsed_time",
|
|
||||||
"section_break_logs",
|
|
||||||
"time_logs"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "naming_series",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Series",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "EXE-CRD-.YYYY.-",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "engagement_order",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Engagement Order",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Engagement Order",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "step_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Step Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_1",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "assigned_employee",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Assigned Employee",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Employee",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "allocated_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Allocated Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "1",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "actual_hours_logged",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Actual Hours Logged",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "2",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "status",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Status",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Open\nRunning\nPaused\nSubmitted",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_timer",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Time Tracking",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_logs",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Time Logs",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "time_logs",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Time Logs",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Execution Card Log",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_field_mappings": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_field": "",
|
|
||||||
"import_source": "",
|
|
||||||
"is_submittable": 1,
|
|
||||||
"is_table": 0,
|
|
||||||
"label": "Execution Card",
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Execution Card",
|
|
||||||
"name_case": "",
|
|
||||||
"nsm_parent_field": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 1,
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"select": 1,
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 1,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
# Copyright (c) 2026, cclohmar and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
class ExecutionCard(Document):
|
|
||||||
def before_save(self):
|
|
||||||
self.calculate_logged_hours()
|
|
||||||
|
|
||||||
def calculate_logged_hours(self):
|
|
||||||
total = 0.0
|
|
||||||
if self.get("time_logs"):
|
|
||||||
for log in self.time_logs:
|
|
||||||
total += log.get("duration_hours", 0) or 0
|
|
||||||
self.actual_hours_logged = total
|
|
||||||
|
|
||||||
def on_update(self):
|
|
||||||
self.update_step_actual_hours()
|
|
||||||
self.update_engagement_order_totals()
|
|
||||||
|
|
||||||
def on_submit(self):
|
|
||||||
self.status = "Submitted"
|
|
||||||
self.update_step_actual_hours()
|
|
||||||
self.update_engagement_order_totals()
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
self.update_step_actual_hours()
|
|
||||||
self.update_engagement_order_totals()
|
|
||||||
|
|
||||||
def update_step_actual_hours(self):
|
|
||||||
"""Push logged hours back to the matching run step
|
|
||||||
in the parent Engagement Order's child table."""
|
|
||||||
if not self.engagement_order or not self.step_name:
|
|
||||||
return
|
|
||||||
|
|
||||||
order = frappe.get_doc("Engagement Order", self.engagement_order)
|
|
||||||
for step in order.get("run_steps", []):
|
|
||||||
if step.step_name == self.step_name:
|
|
||||||
step.actual_hours = self.actual_hours_logged or 0
|
|
||||||
order.save()
|
|
||||||
break
|
|
||||||
|
|
||||||
def update_engagement_order_totals(self):
|
|
||||||
"""Recalculate parent order totals."""
|
|
||||||
if not self.engagement_order:
|
|
||||||
return
|
|
||||||
|
|
||||||
order = frappe.get_doc("Engagement Order", self.engagement_order)
|
|
||||||
order.calculate_totals()
|
|
||||||
order.save()
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def start_work(self):
|
|
||||||
"""Start work on this execution card."""
|
|
||||||
if self.status not in ("Open", "Paused"):
|
|
||||||
frappe.throw(_("Card must be in 'Open' or 'Paused' status to start work."))
|
|
||||||
|
|
||||||
log = self.append("time_logs", {})
|
|
||||||
log.start_time = frappe.utils.now_datetime()
|
|
||||||
self.status = "Running"
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def pause_work(self):
|
|
||||||
"""Pause work on this execution card."""
|
|
||||||
if self.status != "Running":
|
|
||||||
frappe.throw(_("Card must be in 'Running' status to pause work."))
|
|
||||||
|
|
||||||
if self.time_logs and not self.time_logs[-1].end_time:
|
|
||||||
log = self.time_logs[-1]
|
|
||||||
log.end_time = frappe.utils.now_datetime()
|
|
||||||
log.duration_hours = (log.end_time - log.start_time).total_seconds() / 3600
|
|
||||||
|
|
||||||
self.calculate_logged_hours()
|
|
||||||
self.status = "Paused"
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def stop_work(self):
|
|
||||||
"""Stop work and mark the card for submission."""
|
|
||||||
if self.status not in ("Running", "Paused"):
|
|
||||||
frappe.throw(_("Card must be 'Running' or 'Paused' to stop work."))
|
|
||||||
|
|
||||||
if self.status == "Running" and self.time_logs and not self.time_logs[-1].end_time:
|
|
||||||
log = self.time_logs[-1]
|
|
||||||
log.end_time = frappe.utils.now_datetime()
|
|
||||||
log.duration_hours = (log.end_time - log.start_time).total_seconds() / 3600
|
|
||||||
|
|
||||||
self.calculate_logged_hours()
|
|
||||||
self.status = "Submitted"
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_active_time(self):
|
|
||||||
"""Return current elapsed time if card is running."""
|
|
||||||
if self.status != "Running" or not self.time_logs:
|
|
||||||
return {"elapsed_seconds": 0}
|
|
||||||
|
|
||||||
log = self.time_logs[-1]
|
|
||||||
if log.start_time and not log.end_time:
|
|
||||||
elapsed = (frappe.utils.now_datetime() - log.start_time).total_seconds()
|
|
||||||
return {"elapsed_seconds": elapsed}
|
|
||||||
|
|
||||||
return {"elapsed_seconds": 0}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"autoname": "hash",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"start_time",
|
|
||||||
"end_time",
|
|
||||||
"duration_hours"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "start_time",
|
|
||||||
"fieldtype": "Datetime",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Start Time",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "end_time",
|
|
||||||
"fieldtype": "Datetime",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "End Time",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "duration_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Duration Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "2",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_field_mappings": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_field": "",
|
|
||||||
"import_source": "",
|
|
||||||
"is_submittable": 0,
|
|
||||||
"is_table": 1,
|
|
||||||
"label": "Execution Card Log",
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Execution Card Log",
|
|
||||||
"name_case": "",
|
|
||||||
"nsm_parent_field": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,335 +0,0 @@
|
||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_auto_repeat": 0,
|
|
||||||
"allow_events_in_timeline": 1,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 1,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "field:center_name",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "Master",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"center_name",
|
|
||||||
"resource_type",
|
|
||||||
"column_break_1",
|
|
||||||
"hourly_cost_rate",
|
|
||||||
"hourly_billing_rate",
|
|
||||||
"section_break_capacity",
|
|
||||||
"daily_capacity_hours",
|
|
||||||
"section_break_members",
|
|
||||||
"members"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 1,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "center_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Center Name",
|
|
||||||
"length": 140,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 1,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "resource_type",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Resource Type",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Engineering\nSales Engineering\nSupport\nConsulting",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_1",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "hourly_cost_rate",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Hourly Cost Rate",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "hourly_billing_rate",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Hourly Billing Rate",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_capacity",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Capacity Planning",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "daily_capacity_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Daily Capacity Hours",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "1",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_members",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Team Members",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "members",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Members",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Resource Center Member",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_field_mappings": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_field": "",
|
|
||||||
"import_source": "",
|
|
||||||
"is_submittable": 0,
|
|
||||||
"is_table": 0,
|
|
||||||
"label": "Resource Center",
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Resource Center",
|
|
||||||
"name_case": "",
|
|
||||||
"nsm_parent_field": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"select": 1,
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# Copyright (c) 2026, cclohmar and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceCenter(Document):
|
|
||||||
pass
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"autoname": "field:employee",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"employee",
|
|
||||||
"allocation_percentage"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "employee",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Employee",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Employee",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "allocation_percentage",
|
|
||||||
"fieldtype": "Percent",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Allocation Percentage",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_field_mappings": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_field": "",
|
|
||||||
"import_source": "",
|
|
||||||
"is_submittable": 0,
|
|
||||||
"is_table": 1,
|
|
||||||
"label": "Resource Center Member",
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Resource Center Member",
|
|
||||||
"name_case": "",
|
|
||||||
"nsm_parent_field": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
264
service_factory/service_factory/hooks.py
Normal file
264
service_factory/service_factory/hooks.py
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
app_name = "service_factory"
|
||||||
|
app_title = "Service Factory"
|
||||||
|
app_publisher = "cclohmar"
|
||||||
|
app_description = "Professional Services Delivery & Capacity Management"
|
||||||
|
app_email = "claus.lohmar@lohmar.co.uk"
|
||||||
|
app_license = "mit"
|
||||||
|
|
||||||
|
# Apps
|
||||||
|
# ------------------
|
||||||
|
|
||||||
|
# required_apps = []
|
||||||
|
|
||||||
|
# Each item in the list will be shown as an app in the apps page
|
||||||
|
add_to_apps_screen = [
|
||||||
|
{
|
||||||
|
"name": "service_factory",
|
||||||
|
"logo": "/assets/service_factory/icons/service_factory.svg",
|
||||||
|
"title": "Service Factory",
|
||||||
|
"route": "/app/engagement-board",
|
||||||
|
"has_permission": "service_factory.api.permission.has_app_permission"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Includes in <head>
|
||||||
|
# ------------------
|
||||||
|
|
||||||
|
# include js, css files in header of desk.html
|
||||||
|
# app_include_css = "/assets/service_factory/css/service_factory.css"
|
||||||
|
# app_include_js = "/assets/service_factory/js/service_factory.js"
|
||||||
|
|
||||||
|
# include js, css files in header of web template
|
||||||
|
# web_include_css = "/assets/service_factory/css/service_factory.css"
|
||||||
|
# web_include_js = "/assets/service_factory/js/service_factory.js"
|
||||||
|
|
||||||
|
# include custom scss in every website theme (without file extension ".scss")
|
||||||
|
# website_theme_scss = "service_factory/public/scss/website"
|
||||||
|
|
||||||
|
# include js, css files in header of web form
|
||||||
|
# webform_include_js = {"doctype": "public/js/doctype.js"}
|
||||||
|
# webform_include_css = {"doctype": "public/css/doctype.css"}
|
||||||
|
|
||||||
|
# include js in page
|
||||||
|
# page_js = {"page" : "public/js/file.js"}
|
||||||
|
|
||||||
|
# include js in doctype views
|
||||||
|
doctype_js = {"Engagement Card" : "doctype/engagement_card/engagement_card.js"}
|
||||||
|
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
|
||||||
|
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
|
||||||
|
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
|
||||||
|
|
||||||
|
# Svg Icons
|
||||||
|
# ------------------
|
||||||
|
# include app icons in desk
|
||||||
|
# app_include_icons = "service_factory/public/icons.svg"
|
||||||
|
|
||||||
|
# Home Pages
|
||||||
|
# ----------
|
||||||
|
|
||||||
|
# application home page (will override Website Settings)
|
||||||
|
# home_page = "login"
|
||||||
|
|
||||||
|
# website user home page (by Role)
|
||||||
|
# role_home_page = {
|
||||||
|
# "Role": "home_page"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Generators
|
||||||
|
# ----------
|
||||||
|
|
||||||
|
# automatically create page for each record of this doctype
|
||||||
|
# website_generators = ["Web Page"]
|
||||||
|
|
||||||
|
# automatically load and sync documents of this doctype from downstream apps
|
||||||
|
# importable_doctypes = [doctype_1]
|
||||||
|
|
||||||
|
# Jinja
|
||||||
|
# ----------
|
||||||
|
|
||||||
|
# add methods and filters to jinja environment
|
||||||
|
# jinja = {
|
||||||
|
# "methods": "service_factory.utils.jinja_methods",
|
||||||
|
# "filters": "service_factory.utils.jinja_filters"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
# before_install = "service_factory.install.before_install"
|
||||||
|
# after_install = "service_factory.install.after_install"
|
||||||
|
|
||||||
|
# Uninstallation
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
# before_uninstall = "service_factory.uninstall.before_uninstall"
|
||||||
|
# after_uninstall = "service_factory.uninstall.after_uninstall"
|
||||||
|
|
||||||
|
# Integration Setup
|
||||||
|
# ------------------
|
||||||
|
# To set up dependencies/integrations with other apps
|
||||||
|
# Name of the app being installed is passed as an argument
|
||||||
|
|
||||||
|
# before_app_install = "service_factory.utils.before_app_install"
|
||||||
|
# after_app_install = "service_factory.utils.after_app_install"
|
||||||
|
|
||||||
|
# Integration Cleanup
|
||||||
|
# -------------------
|
||||||
|
# To clean up dependencies/integrations with other apps
|
||||||
|
# Name of the app being uninstalled is passed as an argument
|
||||||
|
|
||||||
|
# before_app_uninstall = "service_factory.utils.before_app_uninstall"
|
||||||
|
# after_app_uninstall = "service_factory.utils.after_app_uninstall"
|
||||||
|
|
||||||
|
# Build
|
||||||
|
# ------------------
|
||||||
|
# To hook into the build process
|
||||||
|
|
||||||
|
# after_build = "service_factory.build.after_build"
|
||||||
|
|
||||||
|
# Desk Notifications
|
||||||
|
# ------------------
|
||||||
|
# See frappe.core.notifications.get_notification_config
|
||||||
|
|
||||||
|
# notification_config = "service_factory.notifications.get_notification_config"
|
||||||
|
|
||||||
|
# Permissions
|
||||||
|
# -----------
|
||||||
|
# Permissions evaluated in scripted ways
|
||||||
|
|
||||||
|
# permission_query_conditions = {
|
||||||
|
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# has_permission = {
|
||||||
|
# "Event": "frappe.desk.doctype.event.event.has_permission",
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Document Events
|
||||||
|
# ---------------
|
||||||
|
# Hook on document methods and events
|
||||||
|
|
||||||
|
# doc_events = {
|
||||||
|
# "*": {
|
||||||
|
# "on_update": "method",
|
||||||
|
# "on_cancel": "method",
|
||||||
|
# "on_trash": "method"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Scheduled Tasks
|
||||||
|
# ---------------
|
||||||
|
|
||||||
|
# scheduler_events = {
|
||||||
|
# "all": [
|
||||||
|
# "service_factory.tasks.all"
|
||||||
|
# ],
|
||||||
|
# "daily": [
|
||||||
|
# "service_factory.tasks.daily"
|
||||||
|
# ],
|
||||||
|
# "hourly": [
|
||||||
|
# "service_factory.tasks.hourly"
|
||||||
|
# ],
|
||||||
|
# "weekly": [
|
||||||
|
# "service_factory.tasks.weekly"
|
||||||
|
# ],
|
||||||
|
# "monthly": [
|
||||||
|
# "service_factory.tasks.monthly"
|
||||||
|
# ],
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Fixtures - seed data synced during bench migrate
|
||||||
|
# Add new DocType records here to make them portable
|
||||||
|
# across all server instances.
|
||||||
|
# -----------------------------------------
|
||||||
|
fixtures = []
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
# -------
|
||||||
|
|
||||||
|
# before_tests = "service_factory.install.before_tests"
|
||||||
|
|
||||||
|
# Extend DocType Class
|
||||||
|
# ------------------------------
|
||||||
|
#
|
||||||
|
# Specify custom mixins to extend the standard doctype controller.
|
||||||
|
# extend_doctype_class = {
|
||||||
|
# "Task": "service_factory.custom.task.CustomTaskMixin"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Overriding Methods
|
||||||
|
# ------------------------------
|
||||||
|
#
|
||||||
|
# override_whitelisted_methods = {
|
||||||
|
# "frappe.desk.doctype.event.event.get_events": "service_factory.event.get_events"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# each overriding function accepts a `data` argument;
|
||||||
|
# generated from the base implementation of the doctype dashboard,
|
||||||
|
# along with any modifications made in other Frappe apps
|
||||||
|
# override_doctype_dashboards = {
|
||||||
|
# "Task": "service_factory.task.get_dashboard_data"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# exempt linked doctypes from being automatically cancelled
|
||||||
|
#
|
||||||
|
# auto_cancel_exempted_doctypes = ["Auto Repeat"]
|
||||||
|
|
||||||
|
# Ignore links to specified DocTypes when deleting documents
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
|
||||||
|
# ignore_links_on_delete = ["Communication", "ToDo"]
|
||||||
|
|
||||||
|
# Request Events
|
||||||
|
# ----------------
|
||||||
|
# before_request = ["service_factory.utils.before_request"]
|
||||||
|
# after_request = ["service_factory.utils.after_request"]
|
||||||
|
|
||||||
|
# Job Events
|
||||||
|
# ----------
|
||||||
|
# before_job = ["service_factory.utils.before_job"]
|
||||||
|
# after_job = ["service_factory.utils.after_job"]
|
||||||
|
|
||||||
|
# User Data Protection
|
||||||
|
# --------------------
|
||||||
|
|
||||||
|
# user_data_fields = [
|
||||||
|
# {
|
||||||
|
# "doctype": "{doctype_1}",
|
||||||
|
# "filter_by": "{filter_by}",
|
||||||
|
# "redact_fields": ["{field_1}", "{field_2}"],
|
||||||
|
# "partial": 1,
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "doctype": "{doctype_2}",
|
||||||
|
# "filter_by": "{filter_by}",
|
||||||
|
# "partial": 1,
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "doctype": "{doctype_3}",
|
||||||
|
# "strict": False,
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "doctype": "{doctype_4}"
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Authentication and authorization
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
# auth_hooks = [
|
||||||
|
# "service_factory.auth.validate"
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Automatically update python controller files with type annotations for this app.
|
||||||
|
# export_python_type_annotations = True
|
||||||
|
|
||||||
|
# default_log_clearing_doctypes = {
|
||||||
|
# "Logging DocType Name": 30 # days to retain logs
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Translation
|
||||||
|
# ------------
|
||||||
|
# List of apps whose translatable strings should be excluded from this app's translations.
|
||||||
|
# ignore_translatable_strings_from = []
|
||||||
|
|
||||||
1
service_factory/service_factory/modules.txt
Normal file
1
service_factory/service_factory/modules.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Service Factory
|
||||||
6
service_factory/service_factory/patches.txt
Normal file
6
service_factory/service_factory/patches.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[pre_model_sync]
|
||||||
|
# Patches added in this section will be executed before doctypes are migrated
|
||||||
|
# Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations
|
||||||
|
|
||||||
|
[post_model_sync]
|
||||||
|
# Patches added in this section will be executed after doctypes are migrated
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
||||||
|
<rect width="48" height="48" rx="8" fill="#2490ef"/>
|
||||||
|
<g fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round">
|
||||||
|
<path d="M10 16h28l-4 8H14l-4-8z"/>
|
||||||
|
<path d="M14 24h20l-3 6H17l-3-6z"/>
|
||||||
|
<path d="M18 30h12l-2 4H20l-2-4z"/>
|
||||||
|
<circle cx="24" cy="12" r="2" fill="#fff" stroke="none"/>
|
||||||
|
</g>
|
||||||
|
<text x="24" y="42" text-anchor="middle" fill="#fff" font-size="6" font-family="sans-serif">SF</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 511 B |
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright (c) 2026, cclohmar and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("Engagement Card", {
|
||||||
|
refresh: function (frm) {
|
||||||
|
// Add quick actions in the form toolbar
|
||||||
|
if (frm.doc.status === "Discovery") {
|
||||||
|
frm.add_custom_button(__("Move to Delivery"), function () {
|
||||||
|
frm.set_value("status", "Delivery");
|
||||||
|
frm.save();
|
||||||
|
});
|
||||||
|
} else if (frm.doc.status === "Delivery") {
|
||||||
|
frm.add_custom_button(__("Mark Complete"), function () {
|
||||||
|
frm.set_value("status", "Complete");
|
||||||
|
frm.save();
|
||||||
|
});
|
||||||
|
} else if (frm.doc.status === "Complete") {
|
||||||
|
frm.add_custom_button(__("Archive"), function () {
|
||||||
|
frm.set_value("status", "Archived");
|
||||||
|
frm.set_value("actual_end_date", frappe.datetime.get_today());
|
||||||
|
frm.save();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add log activity quick button
|
||||||
|
frm.add_custom_button(__("Log Activity"), function () {
|
||||||
|
let d = new frappe.ui.Dialog({
|
||||||
|
title: __("Log Activity"),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: "Activity Type",
|
||||||
|
fieldname: "activity_type",
|
||||||
|
fieldtype: "Select",
|
||||||
|
options: ["Comment", "Milestone", "Note"],
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Content",
|
||||||
|
fieldname: "content",
|
||||||
|
fieldtype: "Text Editor",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Mark as Milestone",
|
||||||
|
fieldname: "is_milestone",
|
||||||
|
fieldtype: "Check",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primary_action: function () {
|
||||||
|
let data = d.get_values();
|
||||||
|
let child = frm.add_child("activity_log");
|
||||||
|
child.posted_by = frappe.session.user;
|
||||||
|
child.timestamp = frappe.datetime.now_datetime();
|
||||||
|
child.activity_type = data.activity_type;
|
||||||
|
child.content = data.content;
|
||||||
|
child.is_milestone = data.is_milestone;
|
||||||
|
frm.refresh_field("activity_log");
|
||||||
|
d.hide();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Engagement Card Time Log", {
|
||||||
|
hours: function (frm, cdt, cdn) {
|
||||||
|
// Trigger parent rollup when hours change in the grid
|
||||||
|
frm.trigger("update_total_hours");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Engagement Card Time Log", {
|
||||||
|
time_log_remove: function (frm) {
|
||||||
|
frm.trigger("update_total_hours");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,259 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_auto_repeat": 0,
|
||||||
|
"allow_events_in_timeline": 1,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
|
"allow_import": 1,
|
||||||
|
"allow_rename": 0,
|
||||||
|
"autoname": "naming_series:",
|
||||||
|
"creation": "2026-07-15 23:00:00.000000",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "Document",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"naming_series",
|
||||||
|
"title",
|
||||||
|
"card_type",
|
||||||
|
"status",
|
||||||
|
"column_break_1",
|
||||||
|
"priority",
|
||||||
|
"customer",
|
||||||
|
"opportunity",
|
||||||
|
"section_break_dates",
|
||||||
|
"start_date",
|
||||||
|
"target_end_date",
|
||||||
|
"column_break_dates_2",
|
||||||
|
"actual_end_date",
|
||||||
|
"section_break_budget",
|
||||||
|
"allocated_budget_hours",
|
||||||
|
"total_logged_hours",
|
||||||
|
"column_break_budget_2",
|
||||||
|
"sales_order",
|
||||||
|
"section_break_desc",
|
||||||
|
"description",
|
||||||
|
"section_break_assignments",
|
||||||
|
"assignments",
|
||||||
|
"section_break_activity",
|
||||||
|
"activity_log",
|
||||||
|
"section_break_time",
|
||||||
|
"time_log",
|
||||||
|
"section_break_subtasks",
|
||||||
|
"subtasks"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Series",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "TEC-.YYYY.-",
|
||||||
|
"print_hide": 1,
|
||||||
|
"reqd": 1,
|
||||||
|
"set_only_once": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_global_search": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Title",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "card_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Card Type",
|
||||||
|
"options": "SE\nPS\nMS\nSupport",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Discovery\nDelivery\nComplete\nArchived",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": "Discovery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "priority",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Priority",
|
||||||
|
"options": "Low\nMedium\nHigh\nCritical",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": "Medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_global_search": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Customer",
|
||||||
|
"options": "Customer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "opportunity",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Opportunity",
|
||||||
|
"options": "Opportunity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_dates",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Timeline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Start Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "target_end_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Target End Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_dates_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "actual_end_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Actual End Date",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_budget",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Budget & Billing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "allocated_budget_hours",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Allocated Budget Hours"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_logged_hours",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Total Logged Hours",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_budget_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sales_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Sales Order",
|
||||||
|
"options": "Sales Order"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_desc",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_assignments",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Team Assignments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "assignments",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Assignments",
|
||||||
|
"options": "Engagement Card Assignment",
|
||||||
|
"reqd": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_activity",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Activity Log"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "activity_log",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Activity Log",
|
||||||
|
"options": "Engagement Card Activity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_time",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Time Tracking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "time_log",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Time Log",
|
||||||
|
"options": "Engagement Card Time Log"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_subtasks",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Subtasks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "subtasks",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Subtasks",
|
||||||
|
"options": "Engagement Card Subtask"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-07-15 23:00:00.000000",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Service Factory",
|
||||||
|
"name": "Engagement Card",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "All",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Copyright (c) 2026, cclohmar and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
|
class EngagementCard(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.set_title()
|
||||||
|
self.update_logged_hours()
|
||||||
|
|
||||||
|
def set_title(self):
|
||||||
|
"""Set card title if not provided."""
|
||||||
|
if not self.title:
|
||||||
|
self.title = f"{self.card_type} - {self.customer or 'Unnamed'}"
|
||||||
|
|
||||||
|
def update_logged_hours(self):
|
||||||
|
"""Roll up total logged hours from time log child table."""
|
||||||
|
total = 0.0
|
||||||
|
if self.get("time_log"):
|
||||||
|
for entry in self.time_log:
|
||||||
|
total += entry.get("hours", 0) or 0
|
||||||
|
self.total_logged_hours = total
|
||||||
|
|
||||||
|
def on_update(self):
|
||||||
|
"""Update rollups on assignment table when time log changes."""
|
||||||
|
self.sync_assignment_hours()
|
||||||
|
|
||||||
|
def sync_assignment_hours(self):
|
||||||
|
"""Push time log hours back to assignment table per employee."""
|
||||||
|
if not self.get("assignments") or not self.get("time_log"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build employee -> total hours from time log
|
||||||
|
employee_hours = {}
|
||||||
|
for entry in self.time_log:
|
||||||
|
emp = entry.get("employee")
|
||||||
|
if emp:
|
||||||
|
employee_hours[emp] = employee_hours.get(emp, 0) + (entry.get("hours", 0) or 0)
|
||||||
|
|
||||||
|
# Update assignment rows
|
||||||
|
for assignment in self.assignments:
|
||||||
|
emp = assignment.get("employee")
|
||||||
|
if emp and emp in employee_hours:
|
||||||
|
assignment.logged_hours = employee_hours[emp]
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_kpi_data():
|
||||||
|
"""Return dashboard KPI data for Engagement Cards."""
|
||||||
|
from frappe.desk.doctype.number_card.number_card import get_number_card
|
||||||
|
|
||||||
|
total_cards = frappe.db.count("Engagement Card")
|
||||||
|
in_delivery = frappe.db.count("Engagement Card", {"status": "Delivery"})
|
||||||
|
in_discovery = frappe.db.count("Engagement Card", {"status": "Discovery"})
|
||||||
|
archived = frappe.db.count("Engagement Card", {"status": "Archived"})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_cards": total_cards,
|
||||||
|
"in_delivery": in_delivery,
|
||||||
|
"in_discovery": in_discovery,
|
||||||
|
"archived": archived,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 0,
|
||||||
|
"autoname": "hash",
|
||||||
|
"creation": "2026-07-15 23:00:00.000000",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "Document",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"posted_by",
|
||||||
|
"timestamp",
|
||||||
|
"activity_type",
|
||||||
|
"content",
|
||||||
|
"is_milestone"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "posted_by",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Posted By",
|
||||||
|
"options": "User",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"default": "Today",
|
||||||
|
"fieldname": "timestamp",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Timestamp",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "activity_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Activity Type",
|
||||||
|
"options": "Comment\nMilestone\nStatus Change\nNote",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "content",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Content"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_milestone",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Is Milestone"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-07-15 23:00:00.000000",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Service Factory",
|
||||||
|
"name": "Engagement Card Activity",
|
||||||
|
"naming_rule": "Random",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 0
|
||||||
|
}
|
||||||
|
|
@ -4,5 +4,5 @@
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class EngagementOrderRunStep(Document):
|
class EngagementCardActivity(Document):
|
||||||
pass
|
pass
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 0,
|
||||||
|
"autoname": "hash",
|
||||||
|
"creation": "2026-07-15 23:00:00.000000",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "Document",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"employee",
|
||||||
|
"role",
|
||||||
|
"allocated_hours",
|
||||||
|
"logged_hours"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "employee",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Employee",
|
||||||
|
"options": "Employee",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "role",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Role"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "allocated_hours",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Allocated Hours"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "logged_hours",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Logged Hours",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-07-15 23:00:00.000000",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Service Factory",
|
||||||
|
"name": "Engagement Card Assignment",
|
||||||
|
"naming_rule": "Random",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 0
|
||||||
|
}
|
||||||
|
|
@ -4,5 +4,5 @@
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class ResourceCenterMember(Document):
|
class EngagementCardAssignment(Document):
|
||||||
pass
|
pass
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 0,
|
||||||
|
"autoname": "hash",
|
||||||
|
"creation": "2026-07-15 23:00:00.000000",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "Document",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"subject",
|
||||||
|
"assigned_to",
|
||||||
|
"status",
|
||||||
|
"due_date"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "subject",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Subject",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "assigned_to",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Assigned To",
|
||||||
|
"options": "Employee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"default": "Open",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Open\nCompleted",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "due_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Due Date"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-07-15 23:00:00.000000",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Service Factory",
|
||||||
|
"name": "Engagement Card Subtask",
|
||||||
|
"naming_rule": "Random",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 0
|
||||||
|
}
|
||||||
|
|
@ -4,5 +4,5 @@
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class EngagementRecipeStep(Document):
|
class EngagementCardSubtask(Document):
|
||||||
pass
|
pass
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 0,
|
||||||
|
"autoname": "hash",
|
||||||
|
"creation": "2026-07-15 23:00:00.000000",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "Document",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"employee",
|
||||||
|
"date",
|
||||||
|
"hours",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "employee",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Employee",
|
||||||
|
"options": "Employee",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"default": "Today",
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "hours",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Hours",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Description"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-07-15 23:00:00.000000",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Service Factory",
|
||||||
|
"name": "Engagement Card Time Log",
|
||||||
|
"naming_rule": "Random",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 0
|
||||||
|
}
|
||||||
|
|
@ -4,5 +4,5 @@
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class ExecutionCardLog(Document):
|
class EngagementCardTimeLog(Document):
|
||||||
pass
|
pass
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
{
|
||||||
|
"charts": [],
|
||||||
|
"content": "[{\"id\":\"board_header_1\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Engagement Board</b></span>\",\"col\":12}},{\"id\":\"board_card_all\",\"type\":\"card\",\"data\":{\"card_name\":\"All Engagement Cards\",\"col\":4}},{\"id\":\"board_card_discovery\",\"type\":\"card\",\"data\":{\"card_name\":\"Discovery Cards\",\"col\":4}},{\"id\":\"board_card_delivery\",\"type\":\"card\",\"data\":{\"card_name\":\"Delivery Cards\",\"col\":4}},{\"id\":\"board_spacer_1\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"board_header_2\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"id\":\"board_shortcut_new\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"New Engagement Card\",\"col\":3}},{\"id\":\"board_shortcut_kpi\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"KPI Dashboard\",\"col\":3}}]",
|
||||||
|
"creation": "2026-07-15 23:00:00.000000",
|
||||||
|
"custom_blocks": [],
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Workspace",
|
||||||
|
"for_user": "",
|
||||||
|
"hide_custom": 0,
|
||||||
|
"icon": "kanban",
|
||||||
|
"idx": 1,
|
||||||
|
"is_hidden": 0,
|
||||||
|
"label": "Engagement Board",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Engagement Cards",
|
||||||
|
"link_count": 2,
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Engagement Card",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Engagement Card",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 1,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Kanban Board",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Engagement Card",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Reporting",
|
||||||
|
"link_count": 1,
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "KPI Dashboard",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Engagement Card",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2026-07-15 23:00:00.000000",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Service Factory",
|
||||||
|
"name": "Engagement Board",
|
||||||
|
"number_cards": [],
|
||||||
|
"owner": "Administrator",
|
||||||
|
"parent_page": "",
|
||||||
|
"public": 1,
|
||||||
|
"quick_lists": [],
|
||||||
|
"roles": [],
|
||||||
|
"sequence_id": 1.0,
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"action": "New",
|
||||||
|
"color": "Blue",
|
||||||
|
"doc_type": "Engagement Card",
|
||||||
|
"format": "{}",
|
||||||
|
"icon": "add",
|
||||||
|
"label": "New Engagement Card",
|
||||||
|
"link_to": "Engagement Card",
|
||||||
|
"stats_filter": "[]",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Green",
|
||||||
|
"doc_view": null,
|
||||||
|
"format": "{}",
|
||||||
|
"icon": "chart",
|
||||||
|
"kanban_board": null,
|
||||||
|
"label": "KPI Dashboard",
|
||||||
|
"link_to": "Engagement Card",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"restrict_to_domain": null,
|
||||||
|
"stats_filter": "[]",
|
||||||
|
"type": "DocType",
|
||||||
|
"url": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Engagement Board",
|
||||||
|
"type": "Workspace"
|
||||||
|
}
|
||||||
0
service_factory/service_factory/templates/__init__.py
Normal file
0
service_factory/service_factory/templates/__init__.py
Normal file
|
|
@ -1,170 +0,0 @@
|
||||||
{
|
|
||||||
"charts": [],
|
|
||||||
"content": "[{\"id\":\"director_header_1\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Financial Margin Ledger</b></span>\",\"col\":12}},{\"id\":\"director_card_margin\",\"type\":\"card\",\"data\":{\"card_name\":\"Margin Overview\",\"col\":6}},{\"id\":\"director_card_pnl\",\"type\":\"card\",\"data\":{\"card_name\":\"P&L Summary\",\"col\":6}},{\"id\":\"director_spacer_1\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"director_header_2\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Delivery Performance</b></span>\",\"col\":12}},{\"id\":\"director_card_efficiency\",\"type\":\"card\",\"data\":{\"card_name\":\"Delivery Efficiency\",\"col\":6}},{\"id\":\"director_card_burn\",\"type\":\"card\",\"data\":{\"card_name\":\"Resource Burn-Down\",\"col\":6}},{\"id\":\"director_spacer_2\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"director_header_3\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Executive KPIs</b></span>\",\"col\":12}},{\"id\":\"director_shortcut_revenue\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Revenue Pipeline\",\"col\":3}},{\"id\":\"director_shortcut_costs\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Costs\",\"col\":3}},{\"id\":\"director_shortcut_margin\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Gross Margin %\",\"col\":3}},{\"id\":\"director_shortcut_utilization\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Utilization Rate\",\"col\":3}}]",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"custom_blocks": [],
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Workspace",
|
|
||||||
"for_user": "",
|
|
||||||
"hide_custom": 0,
|
|
||||||
"icon": "chart",
|
|
||||||
"idx": 1,
|
|
||||||
"is_hidden": 0,
|
|
||||||
"label": "Director Workspace",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Financial Overview",
|
|
||||||
"link_count": 3,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Engagement Order",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 1,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Execution Card",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 1,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Resource Center",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Resource Center",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Analytics",
|
|
||||||
"link_count": 4,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "All Orders",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Completed Orders",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Engagement Recipes",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Recipe",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Submitted Cards",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Director Workspace",
|
|
||||||
"number_cards": [],
|
|
||||||
"owner": "Administrator",
|
|
||||||
"parent_page": "",
|
|
||||||
"public": 1,
|
|
||||||
"quick_lists": [],
|
|
||||||
"roles": [],
|
|
||||||
"sequence_id": 3.0,
|
|
||||||
"shortcuts": [
|
|
||||||
{
|
|
||||||
"action": "List",
|
|
||||||
"color": "Green",
|
|
||||||
"doc_type": "Engagement Order",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "currency",
|
|
||||||
"label": "Revenue Pipeline",
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"stats_filter": "[[\"Engagement Order\",\"docstatus\",\"=\",1],[\"Engagement Order\",\"status\",\"!=\",\"Cancelled\"]]",
|
|
||||||
"type": "DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "List",
|
|
||||||
"color": "Red",
|
|
||||||
"doc_type": "Execution Card",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "cost",
|
|
||||||
"label": "Delivery Costs",
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"stats_filter": "[[\"Execution Card\",\"status\",\"=\",\"Submitted\"]]",
|
|
||||||
"type": "DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "Report",
|
|
||||||
"color": "Blue",
|
|
||||||
"doc_type": "Engagement Order",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "percent",
|
|
||||||
"label": "Gross Margin %",
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"stats_filter": "[]",
|
|
||||||
"type": "DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "List",
|
|
||||||
"color": "Orange",
|
|
||||||
"doc_type": "Resource Center",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "chart",
|
|
||||||
"label": "Utilization Rate",
|
|
||||||
"link_to": "Resource Center",
|
|
||||||
"stats_filter": "[]",
|
|
||||||
"type": "DocType"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Director Workspace",
|
|
||||||
"type": "Workspace"
|
|
||||||
}
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
{
|
|
||||||
"charts": [],
|
|
||||||
"content": "[{\"id\":\"engineer_header_1\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>My Active Deliverables</b></span>\",\"col\":12}},{\"id\":\"engineer_card_my_cards\",\"type\":\"card\",\"data\":{\"card_name\":\"My Execution Cards\",\"col\":4}},{\"id\":\"engineer_card_orders\",\"type\":\"card\",\"data\":{\"card_name\":\"My Engagement Orders\",\"col\":4}},{\"id\":\"engineer_card_reports\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"engineer_spacer_1\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"engineer_header_2\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Time Tracking</b></span>\",\"col\":12}},{\"id\":\"engineer_card_timer\",\"type\":\"card\",\"data\":{\"card_name\":\"Execution Card Timer\",\"col\":6}},{\"id\":\"engineer_card_hours\",\"type\":\"card\",\"data\":{\"card_name\":\"My Hours This Week\",\"col\":6}},{\"id\":\"engineer_spacer_2\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"engineer_header_3\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Actions</b></span>\",\"col\":12}},{\"id\":\"engineer_shortcut_new_card\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"New Execution Card\",\"col\":3}},{\"id\":\"engineer_shortcut_active\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"My Active Cards\",\"col\":3}}]",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"custom_blocks": [],
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Workspace",
|
|
||||||
"for_user": "",
|
|
||||||
"hide_custom": 0,
|
|
||||||
"icon": "workflow",
|
|
||||||
"idx": 1,
|
|
||||||
"is_hidden": 0,
|
|
||||||
"label": "Engineer Workspace",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "My Execution Cards",
|
|
||||||
"link_count": 3,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Execution Card",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 1,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "My Open Cards",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "My Submitted Cards",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "My Engagement Orders",
|
|
||||||
"link_count": 2,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Engagement Order",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 1,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "My Active Orders",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Reports",
|
|
||||||
"link_count": 1,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "My Hours",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Engineer Workspace",
|
|
||||||
"number_cards": [],
|
|
||||||
"owner": "Administrator",
|
|
||||||
"parent_page": "",
|
|
||||||
"public": 1,
|
|
||||||
"quick_lists": [],
|
|
||||||
"roles": [],
|
|
||||||
"sequence_id": 1.0,
|
|
||||||
"shortcuts": [
|
|
||||||
{
|
|
||||||
"action": "New",
|
|
||||||
"color": "Blue",
|
|
||||||
"doc_type": "Execution Card",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "add",
|
|
||||||
"label": "New Execution Card",
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"stats_filter": "[]",
|
|
||||||
"type": "DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "List",
|
|
||||||
"color": "Grey",
|
|
||||||
"doc_type": "Execution Card",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "list",
|
|
||||||
"label": "My Active Cards",
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"stats_filter": "[[\"Execution Card\",\"assigned_employee\",\"=\",\"__user_employee__\"],[\"Execution Card\",\"status\",\"in\",[\"Open\",\"Running\",\"Paused\"]]]",
|
|
||||||
"type": "DocType"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Engineer Workspace",
|
|
||||||
"type": "Workspace"
|
|
||||||
}
|
|
||||||
|
|
@ -1,221 +0,0 @@
|
||||||
{
|
|
||||||
"charts": [],
|
|
||||||
"content": "[{\"id\":\"manager_header_1\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Operational Overview</b></span>\",\"col\":12}},{\"id\":\"manager_card_orders\",\"type\":\"card\",\"data\":{\"card_name\":\"Engagement Orders\",\"col\":4}},{\"id\":\"manager_card_centers\",\"type\":\"card\",\"data\":{\"card_name\":\"Resource Centers\",\"col\":4}},{\"id\":\"manager_card_recipes\",\"type\":\"card\",\"data\":{\"card_name\":\"Engagement Recipes\",\"col\":4}},{\"id\":\"manager_spacer_1\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"manager_header_2\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Resource Capacity</b></span>\",\"col\":12}},{\"id\":\"manager_card_gantt\",\"type\":\"card\",\"data\":{\"card_name\":\"Schedule Gantt\",\"col\":6}},{\"id\":\"manager_card_alerts\",\"type\":\"card\",\"data\":{\"card_name\":\"Capacity Alerts\",\"col\":6}},{\"id\":\"manager_spacer_2\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"manager_header_3\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Key Metrics</b></span>\",\"col\":12}},{\"id\":\"manager_shortcut_active\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Active Engagements\",\"col\":3}},{\"id\":\"manager_shortcut_stalled\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stalled / Blocked\",\"col\":3}},{\"id\":\"manager_shortcut_capacity\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Capacity Overview\",\"col\":3}},{\"id\":\"manager_shortcut_executions\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"All Execution Cards\",\"col\":3}}]",
|
|
||||||
"creation": "2026-07-15 19:00:00.000000",
|
|
||||||
"custom_blocks": [],
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Workspace",
|
|
||||||
"for_user": "",
|
|
||||||
"hide_custom": 0,
|
|
||||||
"icon": "share",
|
|
||||||
"idx": 1,
|
|
||||||
"is_hidden": 0,
|
|
||||||
"label": "Manager Workspace",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Engagement Orders",
|
|
||||||
"link_count": 4,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Engagement Order",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 1,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Scheduled Orders",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "In Progress Orders",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Completed Orders",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Resource Centers",
|
|
||||||
"link_count": 2,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Resource Center",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Resource Center",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 1,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Resource Center Members",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Resource Center Member",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Engagement Recipes",
|
|
||||||
"link_count": 2,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Engagement Recipe",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Recipe",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 1,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Engagement Recipe Steps",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Engagement Recipe Step",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Execution Tracking",
|
|
||||||
"link_count": 2,
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Execution Card",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 1,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Execution Card Logs",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Execution Card Log",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modified": "2026-07-15 19:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Service Factory",
|
|
||||||
"name": "Manager Workspace",
|
|
||||||
"number_cards": [],
|
|
||||||
"owner": "Administrator",
|
|
||||||
"parent_page": "",
|
|
||||||
"public": 1,
|
|
||||||
"quick_lists": [],
|
|
||||||
"roles": [],
|
|
||||||
"sequence_id": 2.0,
|
|
||||||
"shortcuts": [
|
|
||||||
{
|
|
||||||
"action": "List",
|
|
||||||
"color": "Blue",
|
|
||||||
"doc_type": "Engagement Order",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "list",
|
|
||||||
"label": "Active Engagements",
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"stats_filter": "[[\"Engagement Order\",\"status\",\"in\",[\"Scheduled\",\"In Progress\"]]]",
|
|
||||||
"type": "DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "List",
|
|
||||||
"color": "Red",
|
|
||||||
"doc_type": "Engagement Order",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "info",
|
|
||||||
"label": "Stalled / Blocked",
|
|
||||||
"link_to": "Engagement Order",
|
|
||||||
"stats_filter": "[[\"Engagement Order\",\"status\",\"=\",\"On Hold\"]]",
|
|
||||||
"type": "DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "List",
|
|
||||||
"color": "Green",
|
|
||||||
"doc_type": "Resource Center",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "organization",
|
|
||||||
"label": "Capacity Overview",
|
|
||||||
"link_to": "Resource Center",
|
|
||||||
"stats_filter": "[]",
|
|
||||||
"type": "DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "List",
|
|
||||||
"color": "Grey",
|
|
||||||
"doc_type": "Execution Card",
|
|
||||||
"format": "{}",
|
|
||||||
"icon": "check",
|
|
||||||
"label": "All Execution Cards",
|
|
||||||
"link_to": "Execution Card",
|
|
||||||
"stats_filter": "[]",
|
|
||||||
"type": "DocType"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Manager Workspace",
|
|
||||||
"type": "Workspace"
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue