Backup current agri_seedling application
This commit is contained in:
@@ -3,3 +3,8 @@ __pycache__/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules/
|
node_modules/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
.env
|
||||||
|
*.sql
|
||||||
|
*.sql.gz
|
||||||
|
|||||||
@@ -1,82 +1,150 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from agri_seedling.agri_seedling.auth_hooks import get_current_worker
|
||||||
|
|
||||||
from agri_seedling.agri_seedling.api.mobile_auth import get_current_field_worker
|
# Only these may ever be created from the mobile app.
|
||||||
|
ALLOWED_DOCTYPES = {
|
||||||
# doctype -> fieldname that should be auto-stamped with the logged-in worker
|
"Scouting Report", "Spray Log Morning", "Spray Log Evening",
|
||||||
ACTIVITY_DOCTYPES = {
|
"Planting Record", "Soaking Record", "Tray Registry",
|
||||||
"Scouting Report": "worker",
|
"Greenhouse Watering Log", "Seedling Watering Log", "Dispatch Record",
|
||||||
"Order Patch": None,
|
"Tray Recycling Record", "Media Mix Batch", "Cover Log",
|
||||||
"Dispatch Record": None,
|
"Customer Interaction", "Order Patch", "Resource Inventory",
|
||||||
"Media Mix Batch": "worker",
|
|
||||||
"Planting Record": "worker",
|
|
||||||
"Cover Log": "worker",
|
|
||||||
"Greenhouse Watering Log": "worker",
|
|
||||||
"Seedling Watering Log": "worker",
|
|
||||||
"Spray Log Morning": "worker",
|
|
||||||
"Spray Log Evening": "worker",
|
|
||||||
"Customer Interaction": None,
|
|
||||||
"Soaking Record": "worker",
|
|
||||||
"Tray Recycling Record": "worker",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _profile():
|
||||||
|
# This calls your hook to ensure the token is valid
|
||||||
|
return frappe.get_doc("Worker Profile", get_current_worker())
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
def _assigned_zones(profile):
|
||||||
def submit_activity(doctype, data):
|
return [r.zone for r in (profile.get("assigned_zones") or []) if r.get("zone")]
|
||||||
"""Generic activity submission endpoint used by the React Native app
|
|
||||||
for all 13 field activities. Requires a valid field-worker JWT."""
|
|
||||||
worker = get_current_field_worker()
|
|
||||||
|
|
||||||
if doctype not in ACTIVITY_DOCTYPES:
|
ACTIVITY_LABEL_TO_DOCTYPE = {
|
||||||
frappe.throw(f"Unknown or disallowed activity doctype: {doctype}")
|
"Scouting": "Scouting Report", "Order Patching": "Order Patch",
|
||||||
|
"Order Dispatching": "Dispatch Record", "Mixing Media": "Media Mix Batch",
|
||||||
if isinstance(data, str):
|
"Planting": "Planting Record", "Covering Seedlings": "Cover Log",
|
||||||
data = json.loads(data)
|
"Watering Greenhouse": "Greenhouse Watering Log", "Watering Seedlings": "Seedling Watering Log",
|
||||||
|
"Spraying Morning": "Spray Log Morning", "Spraying Evening": "Spray Log Evening",
|
||||||
data["doctype"] = doctype
|
"Customer Engagement": "Customer Interaction", "Soaking": "Soaking Record",
|
||||||
|
"Recycling Trays": "Tray Recycling Record",
|
||||||
worker_field = ACTIVITY_DOCTYPES[doctype]
|
}
|
||||||
if worker_field:
|
|
||||||
data[worker_field] = worker["worker_id"]
|
|
||||||
|
|
||||||
doc = frappe.get_doc(data)
|
|
||||||
doc.insert(ignore_permissions=True)
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
return {"name": doc.name, "doctype": doctype, "status": "submitted"}
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def my_activities(doctype, limit=20):
|
|
||||||
"""Returns the logged-in worker's own recent submissions for a
|
|
||||||
given activity doctype (field workers can only see their own, per
|
|
||||||
the row-level permission rule in Sec 9.2 of the manual)."""
|
|
||||||
worker = get_current_field_worker()
|
|
||||||
|
|
||||||
if doctype not in ACTIVITY_DOCTYPES:
|
|
||||||
frappe.throw(f"Unknown or disallowed activity doctype: {doctype}")
|
|
||||||
|
|
||||||
worker_field = ACTIVITY_DOCTYPES[doctype]
|
|
||||||
filters = {worker_field: worker["worker_id"]} if worker_field else {}
|
|
||||||
|
|
||||||
return frappe.get_list(
|
|
||||||
doctype,
|
|
||||||
filters=filters,
|
|
||||||
fields=["name", "creation"],
|
|
||||||
order_by="creation desc",
|
|
||||||
limit_page_length=int(limit),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def _assigned_activities(profile):
|
||||||
|
labels = [r.activity_type for r in (profile.get("assigned_activity_types") or []) if r.get("activity_type")]
|
||||||
|
return [ACTIVITY_LABEL_TO_DOCTYPE.get(label, label) for label in labels]
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_reference_data():
|
def get_reference_data():
|
||||||
"""Dropdown/reference data the mobile app needs to populate forms
|
"""Dropdown data, scoped to the caller. The app caches this for offline use."""
|
||||||
(zones, varieties, customers). Requires a valid field-worker JWT."""
|
profile = _profile()
|
||||||
get_current_field_worker()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"greenhouse_zones": frappe.get_all("Greenhouse Zone", fields=["name"]),
|
"zones": _assigned_zones(profile),
|
||||||
"seed_varieties": frappe.get_all("Seed Variety", fields=["name"]),
|
"activity_types": _assigned_activities(profile),
|
||||||
"customers": frappe.get_all("Customer Profile", fields=["name"]),
|
"crops": frappe.get_all("Crop", pluck="name", limit_page_length=0)
|
||||||
}
|
if frappe.db.table_exists("Crop") else [],
|
||||||
|
"pests": frappe.get_all("Pest", pluck="name", limit_page_length=0)
|
||||||
|
if frappe.db.table_exists("Pest") else [],
|
||||||
|
"chemicals": frappe.get_all("Chemical", pluck="name", limit_page_length=0)
|
||||||
|
if frappe.db.table_exists("Chemical") else [],
|
||||||
|
"resources": frappe.get_all("Resource Inventory", pluck="resource_name", limit_page_length=0)
|
||||||
|
if frappe.db.table_exists("Resource Inventory") else [],
|
||||||
|
"seed_varieties": frappe.get_all("Seed Variety", pluck="name", limit_page_length=0)
|
||||||
|
if frappe.db.table_exists("Seed Variety") else [],
|
||||||
|
"tray_types": frappe.get_all("Tray Type", pluck="name", limit_page_length=0)
|
||||||
|
if frappe.db.table_exists("Tray Type") else [],
|
||||||
|
"media_batches": frappe.get_all("Media Mix Batch", pluck="name", order_by="creation desc", limit_page_length=20)
|
||||||
|
if frappe.db.table_exists("Media Mix Batch") else [],
|
||||||
|
"customers": frappe.get_all("Customer", pluck="name", limit_page_length=0)
|
||||||
|
if frappe.db.table_exists("Customer") else [],
|
||||||
|
"customer_profiles": frappe.get_all("Customer Profile", pluck="name", limit_page_length=0)
|
||||||
|
if frappe.db.table_exists("Customer Profile") else [],
|
||||||
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def submit_activity(doctype=None, data=None):
|
||||||
|
"""Server-side enforcement of assignments."""
|
||||||
|
if doctype is None or data is None:
|
||||||
|
# form_dict can arrive empty for JSON bodies in this app's request
|
||||||
|
# flow; fall back to reading the raw body directly.
|
||||||
|
try:
|
||||||
|
raw_body = frappe.local.request.get_data(as_text=True)
|
||||||
|
if raw_body:
|
||||||
|
body = json.loads(raw_body)
|
||||||
|
doctype = doctype or body.get("doctype")
|
||||||
|
data = data or body.get("data")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not doctype or data is None:
|
||||||
|
frappe.throw(_("Missing doctype or data in submission."), frappe.ValidationError)
|
||||||
|
|
||||||
|
worker_id = get_current_worker()
|
||||||
|
profile = frappe.get_doc("Worker Profile", worker_id)
|
||||||
|
|
||||||
|
if doctype not in ALLOWED_DOCTYPES:
|
||||||
|
frappe.throw(_("{0} cannot be submitted from the mobile app.").format(doctype), frappe.PermissionError)
|
||||||
|
|
||||||
|
if doctype not in _assigned_activities(profile):
|
||||||
|
frappe.throw(_("You are not assigned to submit {0}.").format(doctype), frappe.PermissionError)
|
||||||
|
|
||||||
|
payload = json.loads(data) if isinstance(data, str) else (data or {})
|
||||||
|
|
||||||
|
doc = frappe.new_doc(doctype)
|
||||||
|
meta = frappe.get_meta(doctype)
|
||||||
|
for key, value in payload.items():
|
||||||
|
if meta.has_field(key):
|
||||||
|
doc.set(key, value)
|
||||||
|
|
||||||
|
if meta.has_field("worker"):
|
||||||
|
doc.set("worker", worker_id)
|
||||||
|
|
||||||
|
# Belt-and-suspenders: don't rely solely on the DocType's schema-level
|
||||||
|
# "__user" default resolving correctly (that's exactly what broke
|
||||||
|
# before — a stray literal "user" string in the JSON instead of the
|
||||||
|
# magic "__user" token). Setting it explicitly here, the same way
|
||||||
|
# "worker" is set, guarantees it works regardless of DocType JSON state.
|
||||||
|
if meta.has_field("staff_member") and not doc.get("staff_member"):
|
||||||
|
doc.set("staff_member", frappe.session.user)
|
||||||
|
|
||||||
|
doc.insert(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"name": doc.name, "doctype": doctype}
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def my_activities(doctype, limit=20):
|
||||||
|
worker_id = get_current_worker()
|
||||||
|
return frappe.get_all(
|
||||||
|
doctype,
|
||||||
|
filters={"worker": worker_id},
|
||||||
|
fields=["name", "creation"],
|
||||||
|
order_by="creation desc",
|
||||||
|
limit_page_length=int(limit),
|
||||||
|
ignore_permissions=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def attach_file_to_record(doctype=None, name=None, fieldname=None, file_url=None):
|
||||||
|
if doctype is None or name is None or fieldname is None or file_url is None:
|
||||||
|
try:
|
||||||
|
raw_body = frappe.local.request.get_data(as_text=True)
|
||||||
|
if raw_body:
|
||||||
|
body = json.loads(raw_body)
|
||||||
|
doctype = doctype or body.get("doctype")
|
||||||
|
name = name or body.get("name")
|
||||||
|
fieldname = fieldname or body.get("fieldname")
|
||||||
|
file_url = file_url or body.get("file_url")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not all([doctype, name, fieldname, file_url]):
|
||||||
|
frappe.throw(_("Missing doctype, name, fieldname or file_url."), frappe.ValidationError)
|
||||||
|
|
||||||
|
worker_id = get_current_worker()
|
||||||
|
owner_worker = frappe.db.get_value(doctype, name, "worker")
|
||||||
|
if owner_worker != worker_id:
|
||||||
|
frappe.throw(_("You can only attach files to your own records."), frappe.PermissionError)
|
||||||
|
|
||||||
|
frappe.db.set_value(doctype, name, fieldname, file_url)
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"ok": True, "file_url": file_url}
|
||||||
|
|||||||
@@ -1,72 +1,86 @@
|
|||||||
import datetime
|
import json
|
||||||
|
import secrets
|
||||||
import frappe
|
import frappe
|
||||||
import jwt
|
from datetime import datetime, timedelta
|
||||||
from frappe.utils import add_days, get_datetime, nowdate
|
from frappe import _
|
||||||
from frappe.utils.password import get_decrypted_password
|
from frappe.utils import now_datetime
|
||||||
|
from frappe.utils.password import check_password, get_decrypted_password
|
||||||
|
|
||||||
|
TOKEN_TTL_HOURS = 24
|
||||||
|
TOKEN_CACHE_PREFIX = "worker_token"
|
||||||
|
|
||||||
def get_jwt_secret():
|
ACTIVITY_LABEL_TO_DOCTYPE = {
|
||||||
secret = frappe.conf.get("agri_seedling_jwt_secret")
|
"Scouting": "Scouting Report", "Order Patching": "Order Patch",
|
||||||
if not secret:
|
"Order Dispatching": "Dispatch Record", "Mixing Media": "Media Mix Batch",
|
||||||
frappe.throw("JWT secret not configured")
|
"Planting": "Planting Record", "Covering Seedlings": "Cover Log",
|
||||||
return secret
|
"Watering Greenhouse": "Greenhouse Watering Log", "Watering Seedlings": "Seedling Watering Log",
|
||||||
|
"Spraying Morning": "Spray Log Morning", "Spraying Evening": "Spray Log Evening",
|
||||||
|
"Customer Engagement": "Customer Interaction", "Soaking": "Soaking Record",
|
||||||
|
"Recycling Trays": "Tray Recycling Record",
|
||||||
|
}
|
||||||
|
|
||||||
|
def issue_token(worker_id):
|
||||||
|
token = secrets.token_urlsafe(32)
|
||||||
|
expires_at = now_datetime() + timedelta(hours=TOKEN_TTL_HOURS)
|
||||||
|
frappe.cache().set_value(
|
||||||
|
f"{TOKEN_CACHE_PREFIX}:{token}",
|
||||||
|
json.dumps({"worker_id": worker_id, "expires_at": expires_at.isoformat()}),
|
||||||
|
expires_in_sec=TOKEN_TTL_HOURS * 3600,
|
||||||
|
)
|
||||||
|
return token, expires_at
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True, methods=["POST"])
|
||||||
def field_worker_login(worker_id, pin):
|
def field_worker_login(worker_id, pin):
|
||||||
if not frappe.db.exists("Worker Profile", worker_id):
|
worker_id = (worker_id or "").strip()
|
||||||
frappe.throw("Invalid worker ID or PIN", frappe.AuthenticationError)
|
pin = (pin or "").strip()
|
||||||
|
|
||||||
worker = frappe.get_doc("Worker Profile", worker_id)
|
if not frappe.db.exists("Worker Profile", worker_id):
|
||||||
|
frappe.throw(f"Worker ID '{worker_id}' not found in system.", frappe.AuthenticationError)
|
||||||
|
|
||||||
if not worker.active:
|
# --- UNIVERSAL PIN CHECK ---
|
||||||
frappe.throw("This worker account is deactivated", frappe.AuthenticationError)
|
authenticated = False
|
||||||
|
|
||||||
|
# Method 1: Standard Frappe Check
|
||||||
|
try:
|
||||||
|
check_password(worker_id, pin, "Worker Profile", "pin_hash")
|
||||||
|
authenticated = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
stored_pin = get_decrypted_password(
|
# Method 2: Decryption Check (for Desk-saved PINs)
|
||||||
"Worker Profile", worker_id, fieldname="pin_hash", raise_exception=False
|
if not authenticated:
|
||||||
)
|
try:
|
||||||
|
stored = get_decrypted_password("Worker Profile", worker_id, fieldname="pin_hash")
|
||||||
|
if stored and str(stored) == str(pin):
|
||||||
|
authenticated = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if not stored_pin or str(stored_pin) != str(pin):
|
# Method 3: Direct DB Match (Fallback)
|
||||||
frappe.throw("Invalid worker ID or PIN", frappe.AuthenticationError)
|
if not authenticated:
|
||||||
|
db_val = frappe.db.get_value("Worker Profile", worker_id, "pin_hash")
|
||||||
|
if db_val == pin: # Sometimes saved as plain text in dev
|
||||||
|
authenticated = True
|
||||||
|
|
||||||
midnight = get_datetime(add_days(nowdate(), 1))
|
if not authenticated:
|
||||||
|
frappe.throw(f"The PIN you entered for {worker_id} is incorrect.", frappe.AuthenticationError)
|
||||||
|
|
||||||
payload = {
|
profile = frappe.get_doc("Worker Profile", worker_id)
|
||||||
"worker_id": worker.name,
|
if not profile.active:
|
||||||
"full_name": worker.full_name,
|
frappe.throw("This worker account is deactivated.", frappe.AuthenticationError)
|
||||||
"role": "Field Worker",
|
|
||||||
"exp": midnight,
|
|
||||||
"iat": datetime.datetime.utcnow(),
|
|
||||||
}
|
|
||||||
token = jwt.encode(payload, get_jwt_secret(), algorithm="HS256")
|
|
||||||
|
|
||||||
return {
|
assigned_activities = [
|
||||||
"token": token,
|
ACTIVITY_LABEL_TO_DOCTYPE.get(row.activity_type, row.activity_type)
|
||||||
"worker_id": worker.name,
|
for row in (profile.get("assigned_activity_types") or []) if row.get("activity_type")
|
||||||
"full_name": worker.full_name,
|
]
|
||||||
"expires_at": str(midnight),
|
|
||||||
"assigned_activity_types": [row.activity_type for row in worker.assigned_activity_types],
|
|
||||||
"assigned_zones": [row.zone for row in worker.assigned_zones] if worker.assigned_zones else [],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
token, expires_at = issue_token(worker_id)
|
||||||
|
return {
|
||||||
|
"token": token,
|
||||||
|
"expires_at": expires_at.isoformat(),
|
||||||
|
"worker_id": profile.name,
|
||||||
|
"full_name": profile.full_name,
|
||||||
|
"assigned_zones": [row.zone for row in (profile.get("assigned_zones") or []) if row.get("zone")],
|
||||||
|
"assigned_activity_types": assigned_activities
|
||||||
|
}
|
||||||
|
|
||||||
def get_current_field_worker():
|
|
||||||
token = frappe.get_request_header("X-Worker-Token")
|
|
||||||
if not token:
|
|
||||||
frappe.throw("Missing X-Worker-Token header", frappe.AuthenticationError)
|
|
||||||
|
|
||||||
try:
|
|
||||||
payload = jwt.decode(token, get_jwt_secret(), algorithms=["HS256"])
|
|
||||||
except jwt.ExpiredSignatureError:
|
|
||||||
frappe.throw("Session expired, please log in again", frappe.AuthenticationError)
|
|
||||||
except jwt.InvalidTokenError:
|
|
||||||
frappe.throw("Invalid session token", frappe.AuthenticationError)
|
|
||||||
|
|
||||||
return payload
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def whoami():
|
|
||||||
worker = get_current_field_worker()
|
|
||||||
return {"authenticated_as": worker}
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import frappe
|
||||||
|
import json
|
||||||
|
from frappe.utils import now_datetime, get_datetime
|
||||||
|
|
||||||
|
TOKEN_CACHE_PREFIX = "worker_token"
|
||||||
|
FIELD_WORKER_PROXY_USER = "field-worker@agriseedling.local"
|
||||||
|
|
||||||
|
def authenticate_worker_token():
|
||||||
|
# Initialize the error as "Missing Token"
|
||||||
|
frappe.local.auth_error = "No X-Worker-Token found in request headers."
|
||||||
|
|
||||||
|
try:
|
||||||
|
token = frappe.get_request_header("X-Worker-Token")
|
||||||
|
if not token:
|
||||||
|
return
|
||||||
|
|
||||||
|
raw = frappe.cache().get_value(f"{TOKEN_CACHE_PREFIX}:{token}")
|
||||||
|
if not raw:
|
||||||
|
frappe.local.auth_error = "Session not found on server. You may have been logged out."
|
||||||
|
return
|
||||||
|
|
||||||
|
payload = json.loads(raw)
|
||||||
|
worker_id = payload.get("worker_id")
|
||||||
|
expires_at_str = payload.get("expires_at")
|
||||||
|
|
||||||
|
if get_datetime(expires_at_str) < now_datetime():
|
||||||
|
frappe.local.auth_error = f"Session for {worker_id} expired at {expires_at_str}."
|
||||||
|
frappe.cache().delete_value(f"{TOKEN_CACHE_PREFIX}:{token}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not frappe.db.exists("Worker Profile", worker_id):
|
||||||
|
frappe.local.auth_error = f"Worker Profile {worker_id} no longer exists."
|
||||||
|
return
|
||||||
|
|
||||||
|
if not frappe.db.exists("User", FIELD_WORKER_PROXY_USER):
|
||||||
|
frappe.local.auth_error = f"Proxy User {FIELD_WORKER_PROXY_USER} is missing. Run the console fix."
|
||||||
|
return
|
||||||
|
|
||||||
|
# Success
|
||||||
|
frappe.local.worker_id = worker_id
|
||||||
|
frappe.local.auth_error = None
|
||||||
|
frappe.logger().info(f"BEFORE set_user form_dict={dict(frappe.form_dict)}")
|
||||||
|
frappe.set_user(FIELD_WORKER_PROXY_USER)
|
||||||
|
frappe.logger().info(f"AFTER set_user form_dict={dict(frappe.form_dict)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.local.auth_error = f"Internal Auth Error: {str(e)}"
|
||||||
|
|
||||||
|
def get_current_worker():
|
||||||
|
worker_id = getattr(frappe.local, "worker_id", None)
|
||||||
|
if not worker_id:
|
||||||
|
# Get the specific error we just found
|
||||||
|
reason = getattr(frappe.local, "auth_error", "Unknown Session Error")
|
||||||
|
frappe.throw(reason, frappe.AuthenticationError)
|
||||||
|
return worker_id
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Chemical", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:chemical_name",
|
||||||
|
"creation": "2026-08-06 03:12:41.247624",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"chemical_name",
|
||||||
|
"category",
|
||||||
|
"active_ingredient",
|
||||||
|
"registration_number",
|
||||||
|
"linked_resource"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "chemical_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Chemical Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "category",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Category",
|
||||||
|
"options": "Fungicide\nPesticide\nFoliar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "active_ingredient",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Active Ingredient"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "registration_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Registration Number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "linked_resource",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Linked Stock Item",
|
||||||
|
"options": "Resource Inventory",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"description": "The Resource Inventory record this chemical's stock is tracked under. Required for spray logs to deduct stock."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:12:41.247624",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Chemical",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Inventory Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"rows_threshold_for_grid_search": 20,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class Chemical(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestChemical(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -92,6 +92,13 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"if_owner": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"row_format": "Dynamic",
|
"row_format": "Dynamic",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Crop", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:crop_name",
|
||||||
|
"creation": "2026-08-06 03:10:23.618416",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"crop_name",
|
||||||
|
"category",
|
||||||
|
"notes",
|
||||||
|
"linked_resource"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "crop_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Crop Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "category",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Category",
|
||||||
|
"options": "Vegetable\nHerb\nFruit\nFlower\nOther"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "notes",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Notes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "linked_resource",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Linked Seed Stock",
|
||||||
|
"options": "Resource Inventory",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"description": "The Resource Inventory record this crop's seed stock is tracked under. Required for planting records to deduct stock."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:10:23.618416",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Crop",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"rows_threshold_for_grid_search": 20,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class Crop(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestCrop(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Customer", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:customer_name",
|
||||||
|
"creation": "2026-08-06 03:12:10.833021",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"customer_name",
|
||||||
|
"phone",
|
||||||
|
"email",
|
||||||
|
"address",
|
||||||
|
"customer_type"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Customer Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "phone",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Phone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "address",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Customer Type",
|
||||||
|
"options": "Retail\nWholesale\nInstitutional"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:12:10.833021",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Customer",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accountant",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"rows_threshold_for_grid_search": 20,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class Customer(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestCustomer(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Customer Interaction", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
+144
-26
@@ -1,30 +1,148 @@
|
|||||||
{
|
{
|
||||||
"doctype": "DocType",
|
"actions": [],
|
||||||
"name": "Customer Interaction",
|
|
||||||
"module": "Agri Seedling",
|
|
||||||
"istable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"naming_series": "CI-.YYYY.-.#####",
|
"creation": "2026-08-05 06:22:53.359748",
|
||||||
"track_changes": 1,
|
"doctype": "DocType",
|
||||||
"fields": [
|
"engine": "InnoDB",
|
||||||
{"fieldname": "interaction_datetime", "label": "Date & Time", "fieldtype": "Datetime", "reqd": 1, "default": "Now"},
|
"field_order": [
|
||||||
{"fieldname": "customer", "label": "Customer", "fieldtype": "Link", "options": "Customer Profile", "reqd": 1, "in_list_view": 1},
|
"interaction_datetime",
|
||||||
{"fieldname": "interaction_type", "label": "Interaction Type", "fieldtype": "Select", "reqd": 1, "in_list_view": 1,
|
"customer",
|
||||||
"options": "Phone Call\nFarm Visit\nEmail Inquiry\nComplaint\nOrder Inquiry"},
|
"interaction_type",
|
||||||
{"fieldname": "seedling_variety", "label": "Seedling Variety / Order of Interest", "fieldtype": "Link", "options": "Seed Variety"},
|
"seedling_variety",
|
||||||
{"fieldname": "column_break_1", "fieldtype": "Column Break"},
|
"column_break_1",
|
||||||
{"fieldname": "summary", "label": "Interaction Summary", "fieldtype": "Small Text", "reqd": 1},
|
"summary",
|
||||||
{"fieldname": "feedback", "label": "Customer Feedback", "fieldtype": "Select", "options": "Positive\nNeutral\nNegative"},
|
"feedback",
|
||||||
{"fieldname": "follow_up_required", "label": "Follow-up Required", "fieldtype": "Check"},
|
"follow_up_required",
|
||||||
{"fieldname": "follow_up_task", "label": "Follow-up Task Description", "fieldtype": "Small Text", "depends_on": "follow_up_required"},
|
"follow_up_task",
|
||||||
{"fieldname": "follow_up_assignee", "label": "Assignee", "fieldtype": "Link", "options": "User", "depends_on": "follow_up_required"},
|
"follow_up_assignee",
|
||||||
{"fieldname": "next_contact_date", "label": "Next Contact Date", "fieldtype": "Date", "depends_on": "follow_up_required"},
|
"next_contact_date",
|
||||||
{"fieldname": "staff_member", "label": "Staff Member", "fieldtype": "Link", "options": "User", "default": "user"}
|
"staff_member",
|
||||||
|
"worker",
|
||||||
|
"naming_series"
|
||||||
],
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "Now",
|
||||||
|
"fieldname": "interaction_datetime",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Date & Time",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Customer",
|
||||||
|
"options": "Customer",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "interaction_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Interaction Type",
|
||||||
|
"options": "Phone Call\nFarm Visit\nEmail Inquiry\nComplaint\nOrder Inquiry",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "seedling_variety",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Crop / Order of Interest",
|
||||||
|
"options": "Crop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "summary",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Interaction Summary",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "feedback",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Customer Feedback",
|
||||||
|
"options": "Positive\nNeutral\nNegative"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "follow_up_required",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Follow-up Required"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "follow_up_required",
|
||||||
|
"fieldname": "follow_up_task",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Follow-up Task Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "follow_up_required",
|
||||||
|
"fieldname": "follow_up_assignee",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Assignee",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "follow_up_required",
|
||||||
|
"fieldname": "next_contact_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Next Contact Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "__user",
|
||||||
|
"fieldname": "staff_member",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Staff Member",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "worker",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Worker",
|
||||||
|
"options": "Worker Profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Series",
|
||||||
|
"options": "CIN-.YYYY.-"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:22:46.763055",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Customer Interaction",
|
||||||
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{"role": "Admin", "read": 1, "write": 1, "create": 1, "delete": 1, "report": 1},
|
{
|
||||||
{"role": "Stock Manager", "read": 1, "write": 1, "create": 1}
|
"create": 1,
|
||||||
]
|
"delete": 1,
|
||||||
}
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"if_owner": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestCustomerInteraction(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Dispatch Record", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -1,37 +1,192 @@
|
|||||||
{
|
{
|
||||||
"doctype": "DocType",
|
"actions": [],
|
||||||
"name": "Dispatch Record",
|
|
||||||
"module": "Agri Seedling",
|
|
||||||
"istable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"is_submittable": 1,
|
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"naming_series": "DSP-.YYYY.-.#####",
|
"creation": "2026-08-05 06:22:58.563978",
|
||||||
"track_changes": 1,
|
"doctype": "DocType",
|
||||||
"fields": [
|
"engine": "InnoDB",
|
||||||
{"fieldname": "order", "label": "Order", "fieldtype": "Link", "options": "Order Patch", "reqd": 1, "in_list_view": 1},
|
"field_order": [
|
||||||
{"fieldname": "customer", "label": "Customer", "fieldtype": "Link", "options": "Customer Profile", "reqd": 1, "fetch_from": "order.customer", "in_list_view": 1},
|
"order",
|
||||||
{"fieldname": "delivery_address", "label": "Delivery Address", "fieldtype": "Small Text"},
|
"customer",
|
||||||
{"fieldname": "column_break_1", "fieldtype": "Column Break"},
|
"delivery_address",
|
||||||
{"fieldname": "quantity_dispatched", "label": "Quantity Dispatched (trays)", "fieldtype": "Int", "reqd": 1},
|
"column_break_1",
|
||||||
{"fieldname": "boxes_used", "label": "Boxes Used", "fieldtype": "Int"},
|
"quantity_dispatched",
|
||||||
{"fieldname": "box_batch_number", "label": "Box Batch Number", "fieldtype": "Data"},
|
"boxes_used",
|
||||||
{"fieldname": "tape_rolls_used", "label": "Tape Rolls Used", "fieldtype": "Float"},
|
"box_batch_number",
|
||||||
{"fieldname": "rope_meters_used", "label": "Rope Meters Used", "fieldtype": "Float"},
|
"tape_rolls_used",
|
||||||
{"fieldname": "section_break_1", "label": "Transport", "fieldtype": "Section Break"},
|
"rope_meters_used",
|
||||||
{"fieldname": "transport_mode", "label": "Transport Mode", "fieldtype": "Select", "options": "Company Vehicle\nThird-Party Courier"},
|
"section_break_1",
|
||||||
{"fieldname": "driver_name", "label": "Driver Name", "fieldtype": "Data"},
|
"transport_mode",
|
||||||
{"fieldname": "vehicle_transport_id", "label": "Vehicle / Transport ID", "fieldtype": "Data"},
|
"driver_name",
|
||||||
{"fieldname": "column_break_2", "fieldtype": "Column Break"},
|
"vehicle_transport_id",
|
||||||
{"fieldname": "dispatch_datetime", "label": "Dispatch Date & Time", "fieldtype": "Datetime", "reqd": 1, "default": "Now"},
|
"column_break_2",
|
||||||
{"fieldname": "fuel_liters", "label": "Fuel Consumed (L)", "fieldtype": "Float"},
|
"dispatch_datetime",
|
||||||
{"fieldname": "quality_check_pass", "label": "Final QC Result", "fieldtype": "Select", "options": "Pass\nFail", "reqd": 1},
|
"fuel_liters",
|
||||||
{"fieldname": "quality_check_notes", "label": "QC Notes", "fieldtype": "Small Text"},
|
"quality_check_pass",
|
||||||
{"fieldname": "invoice_reference", "label": "Invoice Reference", "fieldtype": "Data", "read_only": 1}
|
"quality_check_notes",
|
||||||
|
"invoice_reference",
|
||||||
|
"naming_series",
|
||||||
|
"amended_from"
|
||||||
],
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Order",
|
||||||
|
"options": "Order Patch",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "order.customer",
|
||||||
|
"fieldname": "customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Customer",
|
||||||
|
"options": "Customer",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "delivery_address",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Delivery Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "quantity_dispatched",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Quantity Dispatched (trays)",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "boxes_used",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Boxes Used"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "box_batch_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Box Batch Number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "tape_rolls_used",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Tape Rolls Used"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "rope_meters_used",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Rope Meters Used"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_1",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Transport"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "transport_mode",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Transport Mode",
|
||||||
|
"options": "Company Vehicle\nThird-Party Courier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "driver_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Driver Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "vehicle_transport_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Vehicle / Transport ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Now",
|
||||||
|
"fieldname": "dispatch_datetime",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Dispatch Date & Time",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fuel_liters",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Fuel Consumed (L)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "quality_check_pass",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Final QC Result",
|
||||||
|
"options": "Pass\nFail",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "quality_check_notes",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "QC Notes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_reference",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Invoice Reference",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Series",
|
||||||
|
"options": "DSR-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Dispatch Record",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:23:16.670402",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Dispatch Record",
|
||||||
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{"role": "Admin", "read": 1, "write": 1, "create": 1, "delete": 1, "submit": 1, "cancel": 1, "amend": 1, "report": 1},
|
{
|
||||||
{"role": "Stock Manager", "read": 1, "write": 1, "create": 1, "submit": 1},
|
"amend": 1,
|
||||||
{"role": "Accountant", "read": 1, "report": 1}
|
"cancel": 1,
|
||||||
]
|
"create": 1,
|
||||||
}
|
"delete": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accountant"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestDispatchRecord(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-07-31 01:52:32.575812",
|
"modified": "2026-08-06 03:29:29.595002",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Agri Seedling",
|
"module": "Agri Seedling",
|
||||||
"name": "Media Mix Batch",
|
"name": "Media Mix Batch",
|
||||||
@@ -114,6 +114,18 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"row_format": "Dynamic",
|
"row_format": "Dynamic",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"requested_delivery_date",
|
"requested_delivery_date",
|
||||||
"greenhouse_zone",
|
"greenhouse_zone",
|
||||||
"worker_team",
|
"worker_team",
|
||||||
|
"worker",
|
||||||
"estimated_readiness_date",
|
"estimated_readiness_date",
|
||||||
"priority_level",
|
"priority_level",
|
||||||
"special_handling_notes",
|
"special_handling_notes",
|
||||||
@@ -59,6 +60,12 @@
|
|||||||
"label": "Worker Team",
|
"label": "Worker Team",
|
||||||
"options": "Worker Profile"
|
"options": "Worker Profile"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "worker",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Worker",
|
||||||
|
"options": "Worker Profile"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "estimated_readiness_date",
|
"fieldname": "estimated_readiness_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
@@ -103,6 +110,13 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"if_owner": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"row_format": "Dynamic",
|
"row_format": "Dynamic",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Pest", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:pest_name",
|
||||||
|
"creation": "2026-08-06 03:11:41.797697",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"pest_name",
|
||||||
|
"pest_type",
|
||||||
|
"notes"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "pest_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Pest / Disease Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pest_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Type",
|
||||||
|
"options": "Pest\nDisease\nNutrient Deficiency\nOther"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "notes",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Notes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:11:41.797697",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Pest",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"rows_threshold_for_grid_search": 20,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class Pest(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestPest(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Planting Record", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -1,33 +1,171 @@
|
|||||||
{
|
{
|
||||||
"doctype": "DocType",
|
"actions": [],
|
||||||
"name": "Planting Record",
|
|
||||||
"module": "Agri Seedling",
|
|
||||||
"istable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"is_submittable": 1,
|
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"naming_series": "PLT-.YYYY.-.#####",
|
"creation": "2026-08-05 06:22:56.549409",
|
||||||
"track_changes": 1,
|
"doctype": "DocType",
|
||||||
"fields": [
|
"engine": "InnoDB",
|
||||||
{"fieldname": "planting_datetime", "label": "Date & Time", "fieldtype": "Datetime", "reqd": 1, "default": "Now"},
|
"field_order": [
|
||||||
{"fieldname": "tray_batch_id", "label": "Tray Batch ID", "fieldtype": "Data", "reqd": 1, "in_list_view": 1},
|
"planting_datetime",
|
||||||
{"fieldname": "seedling_variety", "label": "Seedling Variety", "fieldtype": "Link", "options": "Seed Variety", "reqd": 1},
|
"tray_batch_id",
|
||||||
{"fieldname": "seed_quantity", "label": "Seed Quantity Used", "fieldtype": "Float", "reqd": 1},
|
"seedling_variety",
|
||||||
{"fieldname": "seed_lot_number", "label": "Seed Lot / Batch Number", "fieldtype": "Data", "reqd": 1},
|
"seed_quantity",
|
||||||
{"fieldname": "column_break_1", "fieldtype": "Column Break"},
|
"seed_lot_number",
|
||||||
{"fieldname": "media_batch", "label": "Media Batch", "fieldtype": "Link", "options": "Media Mix Batch"},
|
"column_break_1",
|
||||||
{"fieldname": "zone", "label": "Greenhouse Zone", "fieldtype": "Link", "options": "Greenhouse Zone", "reqd": 1},
|
"media_batch",
|
||||||
{"fieldname": "worker", "label": "Worker", "fieldtype": "Link", "options": "Worker Profile", "reqd": 1},
|
"zone",
|
||||||
{"fieldname": "team_size", "label": "Team Size", "fieldtype": "Int"},
|
"worker",
|
||||||
{"fieldname": "section_break_1", "label": "Tray Configuration", "fieldtype": "Section Break"},
|
"team_size",
|
||||||
{"fieldname": "tray_count", "label": "Tray Count", "fieldtype": "Int", "reqd": 1},
|
"section_break_1",
|
||||||
{"fieldname": "cell_rows", "label": "Cell Rows", "fieldtype": "Int"},
|
"tray_count",
|
||||||
{"fieldname": "cell_columns", "label": "Cell Columns", "fieldtype": "Int"},
|
"cell_rows",
|
||||||
{"fieldname": "seeds_per_cell", "label": "Seeds per Cell", "fieldtype": "Int"},
|
"cell_columns",
|
||||||
{"fieldname": "expected_germination_date", "label": "Expected Germination Date", "fieldtype": "Date"}
|
"seeds_per_cell",
|
||||||
|
"expected_germination_date",
|
||||||
|
"naming_series",
|
||||||
|
"amended_from"
|
||||||
],
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "Now",
|
||||||
|
"fieldname": "planting_datetime",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Date & Time",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "tray_batch_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Tray Batch ID",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "seedling_variety",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Crop",
|
||||||
|
"options": "Crop",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "seed_quantity",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Seed Quantity Used",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "seed_lot_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Seed Lot / Batch Number",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "media_batch",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Media Batch",
|
||||||
|
"options": "Media Mix Batch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "zone",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Greenhouse Zone",
|
||||||
|
"options": "Greenhouse Zone",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "worker",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Worker",
|
||||||
|
"options": "Worker Profile",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "team_size",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Team Size"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_1",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Tray Configuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "tray_count",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Tray Count",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cell_rows",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Cell Rows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cell_columns",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Cell Columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "seeds_per_cell",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Seeds per Cell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "expected_germination_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Expected Germination Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Series",
|
||||||
|
"options": "PLR-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Planting Record",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:22:07.512082",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Planting Record",
|
||||||
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{"role": "Admin", "read": 1, "write": 1, "create": 1, "delete": 1, "submit": 1, "cancel": 1, "amend": 1, "report": 1},
|
{
|
||||||
{"role": "Field Worker", "read": 1, "create": 1, "submit": 1, "if_owner": 1}
|
"amend": 1,
|
||||||
]
|
"cancel": 1,
|
||||||
}
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"if_owner": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestPlantingRecord(FrappeTestCase):
|
||||||
|
pass
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"doctype": "DocType",
|
||||||
|
"name": "Resource Consumption Log",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"istable": 0,
|
||||||
|
"issingle": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
|
"autoname": "hash",
|
||||||
|
"track_changes": 0,
|
||||||
|
"sort_field": "logged_on",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "resource",
|
||||||
|
"label": "Resource",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Resource Inventory",
|
||||||
|
"reqd": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "category",
|
||||||
|
"label": "Category",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"fetch_from": "resource.category",
|
||||||
|
"read_only": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "unit_of_measure",
|
||||||
|
"label": "Unit",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"fetch_from": "resource.unit_of_measure",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "quantity",
|
||||||
|
"label": "Quantity Used",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"reqd": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"description": "Positive = consumed. Negative = reversed (e.g. on cancellation)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "logged_on",
|
||||||
|
"label": "Logged On",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"default": "Now",
|
||||||
|
"reqd": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "zone",
|
||||||
|
"label": "Zone",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Greenhouse Zone",
|
||||||
|
"in_standard_filter": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_1",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "source_doctype",
|
||||||
|
"label": "Source Doctype",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "DocType",
|
||||||
|
"in_standard_filter": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "source_name",
|
||||||
|
"label": "Source Record",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "source_doctype"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"role": "Admin",
|
||||||
|
"read": 1,
|
||||||
|
"write": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"report": 1,
|
||||||
|
"export": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"export": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Inventory Manager",
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"export": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accountant",
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"export": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceConsumptionLog(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Resource Inventory", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -1,39 +1,161 @@
|
|||||||
{
|
{
|
||||||
"doctype": "DocType",
|
"actions": [],
|
||||||
"name": "Resource Inventory",
|
|
||||||
"module": "Agri Seedling",
|
|
||||||
"custom": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"autoname": "field:resource_name",
|
"autoname": "field:resource_name",
|
||||||
"track_changes": 1,
|
"creation": "2026-08-05 06:22:52.194635",
|
||||||
"fields": [
|
"doctype": "DocType",
|
||||||
{"fieldname": "resource_name", "label": "Resource Name", "fieldtype": "Data", "reqd": 1, "unique": 1, "in_list_view": 1},
|
"engine": "InnoDB",
|
||||||
{"fieldname": "category", "label": "Category", "fieldtype": "Select", "in_list_view": 1, "reqd": 1,
|
"field_order": [
|
||||||
"options": "Labor\nWater\nFungicides\nPesticides\nSeeds\nFoliar Feeds\nElectricity\nBoxes\nTapes\nRope\nTransport\nJigs\nFuel"},
|
"resource_name",
|
||||||
{"fieldname": "unit_of_measure", "label": "Unit of Measure", "fieldtype": "Select",
|
"category",
|
||||||
"options": "Person-hours\nLiters\nGrams\nCount\nkWh\nRolls\nMeters\nTrips\nKilometers"},
|
"unit_of_measure",
|
||||||
{"fieldname": "column_break_1", "fieldtype": "Column Break"},
|
"column_break_1",
|
||||||
{"fieldname": "current_stock", "label": "Current Stock", "fieldtype": "Float", "in_list_view": 1},
|
"current_stock",
|
||||||
{"fieldname": "reorder_threshold", "label": "Reorder Threshold", "fieldtype": "Float",
|
"reorder_threshold",
|
||||||
"description": "e.g. 7-day supply equivalent for fungicides, 50 units for boxes — see Appendix C."},
|
"reorder_quantity",
|
||||||
{"fieldname": "reorder_quantity", "label": "Standard Reorder Quantity", "fieldtype": "Float"},
|
"alert_priority",
|
||||||
{"fieldname": "alert_priority", "label": "Alert Priority", "fieldtype": "Select", "options": "LOW\nNORMAL\nHIGH\nCRITICAL", "default": "NORMAL"},
|
"section_break_1",
|
||||||
{"fieldname": "section_break_1", "label": "Batch / Traceability", "fieldtype": "Section Break"},
|
"batch_number",
|
||||||
{"fieldname": "batch_number", "label": "Batch / Lot Number", "fieldtype": "Data"},
|
"supplier",
|
||||||
{"fieldname": "supplier", "label": "Supplier", "fieldtype": "Data"},
|
"expiry_date",
|
||||||
{"fieldname": "expiry_date", "label": "Expiry Date", "fieldtype": "Date"},
|
"registration_number",
|
||||||
{"fieldname": "registration_number", "label": "Chemical Registration Number", "fieldtype": "Data",
|
"cost_rate",
|
||||||
"description": "Required for fungicides/pesticides per Sec 9.3 compliance."},
|
"approval_required_role"
|
||||||
{"fieldname": "cost_rate", "label": "Cost Rate (per unit)", "fieldtype": "Currency"},
|
|
||||||
{"fieldname": "approval_required_role", "label": "Approval Required From", "fieldtype": "Select",
|
|
||||||
"options": "\nStock Manager\nAdmin\nAdmin + Stock Manager"}
|
|
||||||
],
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "resource_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Resource Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "category",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Category",
|
||||||
|
"options": "Labor\nWater\nFungicides\nPesticides\nSeeds\nFoliar Feeds\nElectricity\nBoxes\nTapes\nRope\nTransport\nJigs\nFuel",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "unit_of_measure",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Unit of Measure",
|
||||||
|
"options": "Person-hours\nLiters\nGrams\nCount\nkWh\nRolls\nMeters\nTrips\nKilometers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "current_stock",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Current Stock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "e.g. 7-day supply equivalent for fungicides, 50 units for boxes \u2014 see Appendix C.",
|
||||||
|
"fieldname": "reorder_threshold",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Reorder Threshold"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reorder_quantity",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Standard Reorder Quantity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "NORMAL",
|
||||||
|
"fieldname": "alert_priority",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Alert Priority",
|
||||||
|
"options": "LOW\nNORMAL\nHIGH\nCRITICAL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_1",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Batch / Traceability"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Batch / Lot Number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Supplier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "expiry_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Expiry Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Required for fungicides/pesticides per Sec 9.3 compliance.",
|
||||||
|
"fieldname": "registration_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Chemical Registration Number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Cost Rate (per unit)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "approval_required_role",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Approval Required From",
|
||||||
|
"options": "\nStock Manager\nAdmin\nAdmin + Stock Manager"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:29:29.970439",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Resource Inventory",
|
||||||
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{"role": "Admin", "read": 1, "write": 1, "create": 1, "delete": 1},
|
{
|
||||||
{"role": "Stock Manager", "read": 1, "write": 1, "create": 1},
|
"create": 1,
|
||||||
{"role": "Inventory Manager", "read": 1, "write": 1, "create": 1},
|
"delete": 1,
|
||||||
{"role": "Accountant", "read": 1}
|
"read": 1,
|
||||||
]
|
"role": "Admin",
|
||||||
}
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Inventory Manager",
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"read": 1,
|
||||||
|
"role": "Accountant"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestResourceInventory(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Scouting Report", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -1,38 +1,186 @@
|
|||||||
{
|
{
|
||||||
"doctype": "DocType",
|
"actions": [],
|
||||||
"name": "Scouting Report",
|
|
||||||
"module": "Agri Seedling",
|
|
||||||
"custom": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"is_submittable": 1,
|
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"naming_series": "SCT-.YYYY.-.#####",
|
"creation": "2026-08-05 06:22:54.691986",
|
||||||
"track_changes": 1,
|
"doctype": "DocType",
|
||||||
"fields": [
|
"engine": "InnoDB",
|
||||||
{"fieldname": "inspection_datetime", "label": "Date & Time", "fieldtype": "Datetime", "reqd": 1, "default": "Now", "in_list_view": 1},
|
"field_order": [
|
||||||
{"fieldname": "zone", "label": "Greenhouse Zone", "fieldtype": "Link", "options": "Greenhouse Zone", "reqd": 1, "in_list_view": 1},
|
"inspection_datetime",
|
||||||
{"fieldname": "worker", "label": "Worker", "fieldtype": "Link", "options": "Worker Profile", "reqd": 1, "in_list_view": 1},
|
"zone",
|
||||||
{"fieldname": "column_break_1", "fieldtype": "Column Break"},
|
"worker",
|
||||||
{"fieldname": "pest_disease_type", "label": "Pest / Disease Type", "fieldtype": "Select", "in_list_view": 1,
|
"column_break_1",
|
||||||
"options": "\nAphids\nWhitefly\nSpider Mites\nDamping Off\nPowdery Mildew\nBacterial Blight\nNutrient Deficiency\nOther"},
|
"pest_disease_type",
|
||||||
{"fieldname": "severity", "label": "Severity (1-5)", "fieldtype": "Int", "reqd": 1, "in_list_view": 1,
|
"severity",
|
||||||
"description": "1 = trace, 5 = severe outbreak. Severity >= 3 triggers a Critical push alert to Admin and Stock Manager."},
|
"seedling_variety",
|
||||||
{"fieldname": "seedling_variety", "label": "Seedling Variety", "fieldtype": "Link", "options": "Seed Variety"},
|
"percent_trays_affected",
|
||||||
{"fieldname": "percent_trays_affected", "label": "Est. % Trays Affected", "fieldtype": "Percent"},
|
"section_break_1",
|
||||||
{"fieldname": "section_break_1", "label": "Evidence", "fieldtype": "Section Break"},
|
"photo",
|
||||||
{"fieldname": "photo", "label": "Photo", "fieldtype": "Attach Image",
|
"gps_latitude",
|
||||||
"description": "Required (min 1) when severity >= 3 — enforced in validate()."},
|
"gps_longitude",
|
||||||
{"fieldname": "gps_latitude", "label": "GPS Latitude", "fieldtype": "Float", "read_only": 1},
|
"notes",
|
||||||
{"fieldname": "gps_longitude", "label": "GPS Longitude", "fieldtype": "Float", "read_only": 1},
|
"section_break_2",
|
||||||
{"fieldname": "notes", "label": "Notes / Observations", "fieldtype": "Small Text"},
|
"recommended_action",
|
||||||
{"fieldname": "section_break_2", "label": "Follow-up", "fieldtype": "Section Break"},
|
"escalated",
|
||||||
{"fieldname": "recommended_action", "label": "Recommended Action Flag", "fieldtype": "Small Text", "read_only": 1},
|
"naming_series",
|
||||||
{"fieldname": "escalated", "label": "Escalated to Admin", "fieldtype": "Check", "read_only": 1, "default": "0"}
|
"amended_from"
|
||||||
],
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "Now",
|
||||||
|
"fieldname": "inspection_datetime",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Date & Time",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "zone",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Greenhouse Zone",
|
||||||
|
"options": "Greenhouse Zone",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "worker",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Worker",
|
||||||
|
"options": "Worker Profile",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pest_disease_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Pest / Disease",
|
||||||
|
"options": "Pest"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "1 = trace, 5 = severe outbreak. Severity >= 3 triggers a Critical push alert to Admin and Stock Manager.",
|
||||||
|
"fieldname": "severity",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Severity (1-5)",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "seedling_variety",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Crop",
|
||||||
|
"options": "Crop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "percent_trays_affected",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"label": "Est. % Trays Affected"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_1",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Evidence"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Required (min 1) when severity >= 3 \u2014 enforced in validate().",
|
||||||
|
"fieldname": "photo",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Photo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "gps_latitude",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "GPS Latitude",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "gps_longitude",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "GPS Longitude",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "notes",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Notes / Observations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_2",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Follow-up"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "recommended_action",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Recommended Action Flag",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "escalated",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Escalated to Admin",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Series",
|
||||||
|
"options": "SCT-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Scouting Report",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:20:01.941408",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Scouting Report",
|
||||||
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{"role": "Admin", "read": 1, "write": 1, "create": 1, "delete": 1, "submit": 1, "cancel": 1, "amend": 1, "report": 1},
|
{
|
||||||
{"role": "Stock Manager", "read": 1, "create": 1, "write": 1, "submit": 1, "report": 1},
|
"amend": 1,
|
||||||
{"role": "Field Worker", "read": 1, "create": 1, "submit": 1, "if_owner": 1}
|
"cancel": 1,
|
||||||
]
|
"create": 1,
|
||||||
}
|
"delete": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"if_owner": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -5,6 +5,11 @@ from frappe.model.document import Document
|
|||||||
|
|
||||||
class ScoutingReport(Document):
|
class ScoutingReport(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
# Incoming severity can arrive as a string (e.g. from mobile form
|
||||||
|
# inputs); coerce it once here so every comparison below is safe.
|
||||||
|
if self.severity not in (None, ""):
|
||||||
|
self.severity = frappe.utils.cint(self.severity)
|
||||||
|
|
||||||
if self.severity and self.severity >= 3 and not self.photo:
|
if self.severity and self.severity >= 3 and not self.photo:
|
||||||
frappe.throw(_("At least one photo is required when severity is 3 or higher."))
|
frappe.throw(_("At least one photo is required when severity is 3 or higher."))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestScoutingReport(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-07-31 01:41:09.884281",
|
"modified": "2026-08-06 03:29:29.238696",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Agri Seedling",
|
"module": "Agri Seedling",
|
||||||
"name": "Seed Variety",
|
"name": "Seed Variety",
|
||||||
@@ -57,6 +57,18 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"row_format": "Dynamic",
|
"row_format": "Dynamic",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"date_time",
|
"date_time",
|
||||||
"greenhouse_zone",
|
"greenhouse_zone",
|
||||||
"chemical_category",
|
"chemical_category",
|
||||||
"product_name",
|
"product",
|
||||||
"registration_number",
|
"registration_number",
|
||||||
"concentration",
|
"concentration",
|
||||||
"volume_prepared_liters",
|
"volume_prepared_liters",
|
||||||
@@ -24,7 +24,8 @@
|
|||||||
"morning_spray_reference",
|
"morning_spray_reference",
|
||||||
"pest_status_change",
|
"pest_status_change",
|
||||||
"weather_update",
|
"weather_update",
|
||||||
"decontamination_confirmed"
|
"decontamination_confirmed",
|
||||||
|
"target_pest"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -46,11 +47,6 @@
|
|||||||
"label": "Chemical Category",
|
"label": "Chemical Category",
|
||||||
"options": "Fungicide\nPesticide\nFoliar"
|
"options": "Fungicide\nPesticide\nFoliar"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "product_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Product Name"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "registration_number",
|
"fieldname": "registration_number",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@@ -129,12 +125,24 @@
|
|||||||
"fieldname": "decontamination_confirmed",
|
"fieldname": "decontamination_confirmed",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Decontamination Confirmed"
|
"label": "Decontamination Confirmed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "product",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Chemical",
|
||||||
|
"options": "Chemical"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "target_pest",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Target Pest",
|
||||||
|
"options": "Pest"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-07-31 01:53:08.496728",
|
"modified": "2026-08-06 03:21:30.352449",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Agri Seedling",
|
"module": "Agri Seedling",
|
||||||
"name": "Spray Log Evening",
|
"name": "Spray Log Evening",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Spray Log Morning", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -1,47 +1,242 @@
|
|||||||
{
|
{
|
||||||
"doctype": "DocType",
|
"actions": [],
|
||||||
"name": "Spray Log Morning",
|
|
||||||
"module": "Agri Seedling",
|
|
||||||
"custom": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"is_submittable": 1,
|
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"naming_series": "SPRM-.YYYY.-.#####",
|
"creation": "2026-08-05 06:22:56.993986",
|
||||||
"track_changes": 1,
|
"doctype": "DocType",
|
||||||
"allow_on_submit": [],
|
"engine": "InnoDB",
|
||||||
"fields": [
|
"field_order": [
|
||||||
{"fieldname": "spray_datetime", "label": "Date & Time", "fieldtype": "Datetime", "reqd": 1, "default": "Now"},
|
"spray_datetime",
|
||||||
{"fieldname": "zone", "label": "Greenhouse Zone / Tray Range", "fieldtype": "Link", "options": "Greenhouse Zone", "reqd": 1, "in_list_view": 1},
|
"zone",
|
||||||
{"fieldname": "worker", "label": "Worker", "fieldtype": "Link", "options": "Worker Profile", "reqd": 1, "in_list_view": 1},
|
"worker",
|
||||||
{"fieldname": "column_break_1", "fieldtype": "Column Break"},
|
"column_break_1",
|
||||||
{"fieldname": "chemical_category", "label": "Chemical Category", "fieldtype": "Select", "reqd": 1, "in_list_view": 1,
|
"chemical_category",
|
||||||
"options": "Fungicide\nPesticide\nFoliar"},
|
"product",
|
||||||
{"fieldname": "product", "label": "Product", "fieldtype": "Link", "options": "Resource Inventory", "reqd": 1},
|
"registration_number",
|
||||||
{"fieldname": "registration_number", "label": "Registration Number", "fieldtype": "Data", "fetch_from": "product.registration_number", "read_only": 1},
|
"concentration",
|
||||||
{"fieldname": "concentration", "label": "Concentration (mL/L or g/L)", "fieldtype": "Float"},
|
"section_break_1",
|
||||||
{"fieldname": "section_break_1", "label": "Volumes", "fieldtype": "Section Break"},
|
"solution_volume_prepared",
|
||||||
{"fieldname": "solution_volume_prepared", "label": "Solution Prepared (L)", "fieldtype": "Float", "reqd": 1},
|
"solution_volume_sprayed",
|
||||||
{"fieldname": "solution_volume_sprayed", "label": "Solution Sprayed (L)", "fieldtype": "Float", "reqd": 1},
|
"water_volume_used",
|
||||||
{"fieldname": "water_volume_used", "label": "Water Used for Dilution (L)", "fieldtype": "Float"},
|
"target",
|
||||||
{"fieldname": "target", "label": "Target (pest / deficiency)", "fieldtype": "Data"},
|
"section_break_2",
|
||||||
{"fieldname": "section_break_2", "label": "PPE Checklist", "fieldtype": "Section Break",
|
"ppe_gloves",
|
||||||
"description": "All four must be checked before this record can be submitted (Sec 9.3)."},
|
"ppe_mask",
|
||||||
{"fieldname": "ppe_gloves", "label": "Gloves", "fieldtype": "Check"},
|
"column_break_2",
|
||||||
{"fieldname": "ppe_mask", "label": "Mask / Respirator", "fieldtype": "Check"},
|
"ppe_goggles",
|
||||||
{"fieldname": "column_break_2", "fieldtype": "Column Break"},
|
"ppe_coveralls",
|
||||||
{"fieldname": "ppe_goggles", "label": "Goggles", "fieldtype": "Check"},
|
"section_break_3",
|
||||||
{"fieldname": "ppe_coveralls", "label": "Coveralls", "fieldtype": "Check"},
|
"ambient_temp_c",
|
||||||
{"fieldname": "section_break_3", "label": "Conditions", "fieldtype": "Section Break"},
|
"wind_speed",
|
||||||
{"fieldname": "ambient_temp_c", "label": "Ambient Temp (°C)", "fieldtype": "Float"},
|
"column_break_3",
|
||||||
{"fieldname": "wind_speed", "label": "Wind Speed", "fieldtype": "Select", "options": "Calm\nLow\nModerate"},
|
"reentry_interval_hours",
|
||||||
{"fieldname": "column_break_3", "fieldtype": "Column Break"},
|
"reentry_lock_expires",
|
||||||
{"fieldname": "reentry_interval_hours", "label": "Re-Entry Interval (hrs)", "fieldtype": "Float"},
|
"ppe_non_compliant",
|
||||||
{"fieldname": "reentry_lock_expires", "label": "Zone Restricted Until", "fieldtype": "Datetime", "read_only": 1},
|
"naming_series",
|
||||||
{"fieldname": "ppe_non_compliant", "label": "PPE Non-Compliance Flagged", "fieldtype": "Check", "read_only": 1, "default": "0"}
|
"amended_from"
|
||||||
],
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "Now",
|
||||||
|
"fieldname": "spray_datetime",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Date & Time",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "zone",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Greenhouse Zone / Tray Range",
|
||||||
|
"options": "Greenhouse Zone",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "worker",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Worker",
|
||||||
|
"options": "Worker Profile",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "chemical_category",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Chemical Category",
|
||||||
|
"options": "Fungicide\nPesticide\nFoliar",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "product",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Chemical",
|
||||||
|
"options": "Chemical",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "product.registration_number",
|
||||||
|
"fieldname": "registration_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Registration Number",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "concentration",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Concentration (mL/L or g/L)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_1",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Volumes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "solution_volume_prepared",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Solution Prepared (L)",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "solution_volume_sprayed",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Solution Sprayed (L)",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "water_volume_used",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Water Used for Dilution (L)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "target",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Target Pest",
|
||||||
|
"options": "Pest"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "All four must be checked before this record can be submitted (Sec 9.3).",
|
||||||
|
"fieldname": "section_break_2",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "PPE Checklist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "ppe_gloves",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Gloves"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "ppe_mask",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Mask / Respirator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "ppe_goggles",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Goggles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "ppe_coveralls",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Coveralls"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_3",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Conditions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ambient_temp_c",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Ambient Temp (\u00b0C)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "wind_speed",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Wind Speed",
|
||||||
|
"options": "Calm\nLow\nModerate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reentry_interval_hours",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Re-Entry Interval (hrs)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reentry_lock_expires",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Zone Restricted Until",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "ppe_non_compliant",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "PPE Non-Compliance Flagged",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Series",
|
||||||
|
"options": "SLM-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Spray Log Morning",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:20:45.930927",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Spray Log Morning",
|
||||||
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{"role": "Admin", "read": 1, "write": 1, "create": 1, "delete": 1, "submit": 1, "cancel": 1, "amend": 1, "report": 1},
|
{
|
||||||
{"role": "Field Worker", "read": 1, "create": 1, "submit": 1, "if_owner": 1}
|
"amend": 1,
|
||||||
]
|
"cancel": 1,
|
||||||
}
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"if_owner": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestSprayLogMorning(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestTrayType(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Tray Type", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:tray_type_name",
|
||||||
|
"creation": "2026-08-06 03:13:09.593649",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"tray_type_name",
|
||||||
|
"cell_count",
|
||||||
|
"material"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "tray_type_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Tray Type Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cell_count",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Cell Count"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "material",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Material"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-08-06 03:13:09.593649",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Tray Type",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Admin",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Field Worker",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"rows_threshold_for_grid_search": 20,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class TrayType(Document):
|
||||||
|
pass
|
||||||
@@ -1,8 +1,18 @@
|
|||||||
// Copyright (c) 2026, Agri Seedling Operations Team and contributors
|
frappe.ui.form.on('Worker Profile', {
|
||||||
// For license information, please see license.txt
|
refresh(frm) {
|
||||||
|
if (!frm.is_new()) {
|
||||||
// frappe.ui.form.on("Worker Profile", {
|
frm.add_custom_button('Generate QR Badge', () => {
|
||||||
// refresh(frm) {
|
frappe.call({
|
||||||
|
method: 'agri_seedling.agri_seedling.api.worker_qr.generate_worker_qr',
|
||||||
// },
|
args: { worker_id: frm.doc.name },
|
||||||
// });
|
callback: (r) => {
|
||||||
|
if (r.message) {
|
||||||
|
frappe.msgprint('QR badge generated.');
|
||||||
|
frm.reload_doc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"align_labels_right": 0,
|
||||||
|
"css": ".badge-card { width: 3.4in; height: 2.1in; border: 2px solid #2E7D6B; border-radius: 10px; padding: 12px 14px; font-family: 'Helvetica Neue', Arial, sans-serif; box-sizing: border-box; display: flex; flex-direction: column; justify-content: space-between; } .badge-header { font-size: 9px; letter-spacing: 1.5px; color: #6B7280; text-transform: uppercase; } .badge-body { display: flex; align-items: center; justify-content: space-between; margin-top: 4px; } .badge-name { font-size: 16px; font-weight: 700; color: #1F3B57; margin: 0 0 2px 0; } .badge-id { font-size: 11px; color: #333; margin: 0; } .badge-phone { font-size: 10px; color: #6B7280; margin-top: 2px; } .badge-qr img { width: 1.15in; height: 1.15in; } .badge-footer { font-size: 8px; color: #9CA3AF; text-align: center; margin-top: 4px; }",
|
||||||
|
"custom_format": 1,
|
||||||
|
"disabled": 0,
|
||||||
|
"doc_type": "Worker Profile",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Print Format",
|
||||||
|
"font": "Default",
|
||||||
|
"html": "<div class=\"badge-card\">\n <div class=\"badge-header\">Agri Seedling · Field Worker ID</div>\n <div class=\"badge-body\">\n <div>\n <p class=\"badge-name\">{{ doc.full_name }}</p>\n <p class=\"badge-id\">ID: {{ doc.name }}</p>\n <p class=\"badge-phone\">{{ doc.phone_number }}</p>\n </div>\n <div class=\"badge-qr\">\n {% if doc.qr_image %}\n <img src=\"{{ doc.qr_image }}\">\n {% endif %}\n </div>\n </div>\n <div class=\"badge-footer\">Scan to log field activity — do not share this badge</div>\n</div>",
|
||||||
|
"line_breaks": 0,
|
||||||
|
"margin_bottom": 5,
|
||||||
|
"margin_left": 5,
|
||||||
|
"margin_right": 5,
|
||||||
|
"margin_top": 5,
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Worker ID Badge",
|
||||||
|
"page_number": "Hide",
|
||||||
|
"print_format_builder": 0,
|
||||||
|
"print_format_type": "Jinja",
|
||||||
|
"raw_printing": 0,
|
||||||
|
"standard": "Yes"
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2026-08-05 00:00:00.000000",
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Daily Resource Consumption",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Resource Inventory",
|
||||||
|
"report_name": "Daily Resource Consumption",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{"role": "Admin"},
|
||||||
|
{"role": "Stock Manager"},
|
||||||
|
{"role": "Inventory Manager"},
|
||||||
|
{"role": "Accountant"}
|
||||||
|
]
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
frappe.query_reports["Daily Resource Usage Summary"] = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
fieldname: "from_date",
|
||||||
|
label: __("From Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.get_today(),
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "to_date",
|
||||||
|
label: __("To Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.get_today(),
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "category",
|
||||||
|
label: __("Category"),
|
||||||
|
fieldtype: "Select",
|
||||||
|
options: "\nLabor\nWater\nFungicides\nPesticides\nSeeds\nFoliar Feeds\nElectricity\nBoxes\nTapes\nRope\nTransport\nJigs\nFuel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "resource",
|
||||||
|
label: __("Resource"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Resource Inventory",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "zone",
|
||||||
|
label: __("Zone"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Greenhouse Zone",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "group_by_zone",
|
||||||
|
label: __("Break Down by Zone"),
|
||||||
|
fieldtype: "Check",
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 1,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2026-08-07 00:00:00.000000",
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"module": "Agri Seedling",
|
||||||
|
"name": "Daily Resource Usage Summary",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Resource Consumption Log",
|
||||||
|
"report_name": "Daily Resource Usage Summary",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{"role": "Admin"},
|
||||||
|
{"role": "Stock Manager"},
|
||||||
|
{"role": "Inventory Manager"},
|
||||||
|
{"role": "Accountant"}
|
||||||
|
]
|
||||||
|
}
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import getdate, today
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
if not filters.from_date:
|
||||||
|
filters.from_date = today()
|
||||||
|
if not filters.to_date:
|
||||||
|
filters.to_date = today()
|
||||||
|
|
||||||
|
columns = get_columns()
|
||||||
|
data = get_data(filters)
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
return [
|
||||||
|
{"label": _("Resource"), "fieldname": "resource", "fieldtype": "Link", "options": "Resource Inventory", "width": 200},
|
||||||
|
{"label": _("Category"), "fieldname": "category", "fieldtype": "Data", "width": 130},
|
||||||
|
{"label": _("Total Used"), "fieldname": "total_used", "fieldtype": "Float", "width": 120},
|
||||||
|
{"label": _("Unit"), "fieldname": "unit_of_measure", "fieldtype": "Data", "width": 100},
|
||||||
|
{"label": _("Events"), "fieldname": "event_count", "fieldtype": "Int", "width": 90},
|
||||||
|
{"label": _("Zone"), "fieldname": "zone", "fieldtype": "Link", "options": "Greenhouse Zone", "width": 130},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(filters):
|
||||||
|
conditions = ["logged_on >= %(from_date)s", "logged_on < DATE_ADD(%(to_date)s, INTERVAL 1 DAY)"]
|
||||||
|
values = {"from_date": getdate(filters.from_date), "to_date": getdate(filters.to_date)}
|
||||||
|
|
||||||
|
if filters.get("category"):
|
||||||
|
conditions.append("category = %(category)s")
|
||||||
|
values["category"] = filters.category
|
||||||
|
|
||||||
|
if filters.get("zone"):
|
||||||
|
conditions.append("zone = %(zone)s")
|
||||||
|
values["zone"] = filters.zone
|
||||||
|
|
||||||
|
if filters.get("resource"):
|
||||||
|
conditions.append("resource = %(resource)s")
|
||||||
|
values["resource"] = filters.resource
|
||||||
|
|
||||||
|
group_by_zone = ", zone" if filters.get("group_by_zone") else ""
|
||||||
|
select_zone = ", zone" if filters.get("group_by_zone") else ", '' as zone"
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
resource,
|
||||||
|
category,
|
||||||
|
unit_of_measure,
|
||||||
|
SUM(quantity) AS total_used,
|
||||||
|
COUNT(*) AS event_count
|
||||||
|
{select_zone}
|
||||||
|
FROM `tabResource Consumption Log`
|
||||||
|
WHERE {" AND ".join(conditions)}
|
||||||
|
GROUP BY resource, category, unit_of_measure {group_by_zone}
|
||||||
|
ORDER BY category, total_used DESC
|
||||||
|
"""
|
||||||
|
return frappe.db.sql(query, values, as_dict=True)
|
||||||
@@ -1,23 +1,51 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.utils import now_datetime
|
||||||
|
|
||||||
|
|
||||||
def deduct_spray_resources(doc, method=None):
|
def deduct_spray_resources(doc, method=None):
|
||||||
if doc.product:
|
if not doc.product:
|
||||||
_deduct(doc.product, doc.solution_volume_sprayed or 0)
|
return
|
||||||
|
resource = frappe.db.get_value("Chemical", doc.product, "linked_resource")
|
||||||
|
if resource:
|
||||||
|
_deduct(resource, doc.solution_volume_sprayed or 0, source_doc=doc)
|
||||||
|
else:
|
||||||
|
_warn_unlinked("Chemical", doc.product, doc)
|
||||||
|
|
||||||
|
|
||||||
def reverse_spray_resources(doc, method=None):
|
def reverse_spray_resources(doc, method=None):
|
||||||
if doc.product:
|
if not doc.product:
|
||||||
_deduct(doc.product, -(doc.solution_volume_sprayed or 0))
|
return
|
||||||
|
resource = frappe.db.get_value("Chemical", doc.product, "linked_resource")
|
||||||
|
if resource:
|
||||||
|
_deduct(resource, -(doc.solution_volume_sprayed or 0), source_doc=doc)
|
||||||
|
|
||||||
|
|
||||||
def deduct_seed_stock(doc, method=None):
|
def deduct_seed_stock(doc, method=None):
|
||||||
seed_resource = frappe.db.get_value(
|
if not doc.seedling_variety:
|
||||||
"Resource Inventory", {"category": "Seeds", "resource_name": ["like", f"%{doc.seedling_variety}%"]}, "name"
|
return
|
||||||
|
resource = frappe.db.get_value("Crop", doc.seedling_variety, "linked_resource")
|
||||||
|
if resource:
|
||||||
|
_deduct(resource, doc.seed_quantity or 0, source_doc=doc)
|
||||||
|
else:
|
||||||
|
_warn_unlinked("Crop", doc.seedling_variety, doc)
|
||||||
|
|
||||||
|
|
||||||
|
def _warn_unlinked(entity_doctype, entity_name, source_doc):
|
||||||
|
"""A Chemical/Crop with no linked_resource means stock silently does not
|
||||||
|
get deducted — that used to fail completely silently. Now it alerts
|
||||||
|
Stock Manager + Admin instead, and skips the deduction."""
|
||||||
|
from agri_seedling.agri_seedling.utils.notifications import _send_push, _users_with_role
|
||||||
|
|
||||||
|
recipients = _users_with_role(["Stock Manager", "Admin"])
|
||||||
|
_send_push(
|
||||||
|
recipients,
|
||||||
|
_(
|
||||||
|
"{0} '{1}' has no linked Resource Inventory item — stock was NOT deducted for {2} {3}. "
|
||||||
|
"Set its Linked Stock Item to fix this."
|
||||||
|
).format(entity_doctype, entity_name, source_doc.doctype, source_doc.name),
|
||||||
|
priority="HIGH",
|
||||||
)
|
)
|
||||||
if seed_resource:
|
|
||||||
_deduct(seed_resource, doc.seed_quantity or 0)
|
|
||||||
|
|
||||||
|
|
||||||
def deduct_dispatch_resources(doc, method=None):
|
def deduct_dispatch_resources(doc, method=None):
|
||||||
@@ -31,17 +59,34 @@ def deduct_dispatch_resources(doc, method=None):
|
|||||||
if qty:
|
if qty:
|
||||||
resource = frappe.db.get_value("Resource Inventory", {"category": category}, "name")
|
resource = frappe.db.get_value("Resource Inventory", {"category": category}, "name")
|
||||||
if resource:
|
if resource:
|
||||||
_deduct(resource, qty)
|
_deduct(resource, qty, source_doc=doc)
|
||||||
|
|
||||||
|
|
||||||
def _deduct(resource_name, qty):
|
def _deduct(resource_name, qty, source_doc=None):
|
||||||
if not qty:
|
if not qty:
|
||||||
return
|
return
|
||||||
current = frappe.db.get_value("Resource Inventory", resource_name, "current_stock") or 0
|
current = frappe.db.get_value("Resource Inventory", resource_name, "current_stock") or 0
|
||||||
frappe.db.set_value("Resource Inventory", resource_name, "current_stock", current - qty)
|
frappe.db.set_value("Resource Inventory", resource_name, "current_stock", current - qty)
|
||||||
|
_log_consumption(resource_name, qty, source_doc)
|
||||||
check_low_stock_threshold_by_name(resource_name)
|
check_low_stock_threshold_by_name(resource_name)
|
||||||
|
|
||||||
|
|
||||||
|
def _log_consumption(resource_name, qty, source_doc=None):
|
||||||
|
"""Write one Resource Consumption Log row per deduction/reversal event,
|
||||||
|
so daily usage can be summed later — current_stock only tells us what's
|
||||||
|
left, not what happened."""
|
||||||
|
log = frappe.new_doc("Resource Consumption Log")
|
||||||
|
log.resource = resource_name
|
||||||
|
log.quantity = qty
|
||||||
|
log.logged_on = now_datetime()
|
||||||
|
if source_doc is not None:
|
||||||
|
log.source_doctype = source_doc.doctype
|
||||||
|
log.source_name = source_doc.name
|
||||||
|
if source_doc.meta.has_field("zone"):
|
||||||
|
log.zone = source_doc.zone
|
||||||
|
log.insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
def check_low_stock_threshold(doc, method=None):
|
def check_low_stock_threshold(doc, method=None):
|
||||||
check_low_stock_threshold_by_name(doc.name)
|
check_low_stock_threshold_by_name(doc.name)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"charts": [],
|
"charts": [],
|
||||||
"content": "[{\"id\": \"header1\", \"type\": \"header\", \"data\": {\"text\": \"<span class=\\\"h4\\\">Field Worker Activities</span>\", \"col\": 12}}, {\"id\": \"84af4cd220\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Scouting Report\", \"col\": 3}}, {\"id\": \"fed0636689\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Spray Log Morning\", \"col\": 3}}, {\"id\": \"3b4d8d4b24\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Planting Record\", \"col\": 3}}, {\"id\": \"387bad0415\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dispatch Record\", \"col\": 3}}, {\"id\": \"3c077375dd\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Tray Registry\", \"col\": 3}}, {\"id\": \"26bd3c9955\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customer Interaction\", \"col\": 3}}, {\"id\": \"09547223ef\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Resource Inventory\", \"col\": 3}}, {\"id\": \"d88bfef704\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Greenhouse Zone\", \"col\": 3}}, {\"id\": \"9cd8688822\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Worker Profile\", \"col\": 3}}, {\"id\": \"a1d8bfa6da\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Order Patch\", \"col\": 3}}, {\"id\": \"b56a0033c4\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Media Mix Batch\", \"col\": 3}}, {\"id\": \"ebcd178c84\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Cover Log\", \"col\": 3}}, {\"id\": \"969823d1d0\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Greenhouse Watering Log\", \"col\": 3}}, {\"id\": \"cf1af5b563\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Seedling Watering Log\", \"col\": 3}}, {\"id\": \"7ef09939b5\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Spray Log Evening\", \"col\": 3}}, {\"id\": \"a21e8411df\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Soaking Record\", \"col\": 3}}, {\"id\": \"b4f78a0f7c\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Tray Recycling Record\", \"col\": 3}}]",
|
"content": "[{\"id\": \"header1\", \"type\": \"header\", \"data\": {\"text\": \"<span class=\\\"h4\\\">Field Worker Activities</span>\", \"col\": 12}}, {\"id\": \"84af4cd220\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Scouting Report\", \"col\": 3}}, {\"id\": \"fed0636689\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Spray Log Morning\", \"col\": 3}}, {\"id\": \"3b4d8d4b24\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Planting Record\", \"col\": 3}}, {\"id\": \"387bad0415\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dispatch Record\", \"col\": 3}}, {\"id\": \"3c077375dd\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Tray Registry\", \"col\": 3}}, {\"id\": \"26bd3c9955\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customer Interaction\", \"col\": 3}}, {\"id\": \"09547223ef\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Resource Inventory\", \"col\": 3}}, {\"id\": \"d88bfef704\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Greenhouse Zone\", \"col\": 3}}, {\"id\": \"9cd8688822\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Worker Profile\", \"col\": 3}}, {\"id\": \"a1d8bfa6da\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Order Patch\", \"col\": 3}}, {\"id\": \"b56a0033c4\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Media Mix Batch\", \"col\": 3}}, {\"id\": \"ebcd178c84\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Cover Log\", \"col\": 3}}, {\"id\": \"969823d1d0\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Greenhouse Watering Log\", \"col\": 3}}, {\"id\": \"cf1af5b563\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Seedling Watering Log\", \"col\": 3}}, {\"id\": \"7ef09939b5\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Spray Log Evening\", \"col\": 3}}, {\"id\": \"a21e8411df\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Soaking Record\", \"col\": 3}}, {\"id\": \"b4f78a0f7c\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Tray Recycling Record\", \"col\": 3}}, {\"id\": \"shortcut_daily_resource_consumption\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Daily Resource Consumption\", \"col\": 4}}, {\"id\": \"shortcut_resource_consumption_log\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Resource Consumption Log\", \"col\": 3}}, {\"id\": \"shortcut_daily_resource_usage_summary\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Daily Resource Usage Summary\", \"col\": 4}}, {\"id\": \"XsxJFHB6xR\", \"type\": \"header\", \"data\": {\"text\": \"<span class=\\\"h4\\\"><b></b></span>\", \"col\": 12}}, {\"id\": \"027be923d5\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Crop\", \"col\": 3}}, {\"id\": \"ccb0706ac7\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Pest\", \"col\": 3}}, {\"id\": \"92ab0d4937\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customer\", \"col\": 3}}, {\"id\": \"d0c7f84e66\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chemical\", \"col\": 3}}, {\"id\": \"ab4828a739\", \"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Tray Type\", \"col\": 3}}]",
|
||||||
"creation": "2026-07-31 01:37:26.854909",
|
"creation": "2026-07-31 01:37:26.854909",
|
||||||
"custom_blocks": [],
|
"custom_blocks": [],
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"is_hidden": 0,
|
"is_hidden": 0,
|
||||||
"label": "Agri Seedling",
|
"label": "Agri Seedling",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-07-31 01:55:10.299652",
|
"modified": "2026-08-07 10:33:14.092545",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Agri Seedling",
|
"module": "Agri Seedling",
|
||||||
"name": "Agri Seedling",
|
"name": "Agri Seedling",
|
||||||
@@ -123,6 +123,56 @@
|
|||||||
"label": "Tray Recycling Record",
|
"label": "Tray Recycling Record",
|
||||||
"link_to": "Tray Recycling Record",
|
"link_to": "Tray Recycling Record",
|
||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "",
|
||||||
|
"label": "Daily Resource Consumption",
|
||||||
|
"link_to": "Daily Resource Consumption",
|
||||||
|
"report_ref_doctype": "Resource Inventory",
|
||||||
|
"type": "Report"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "List",
|
||||||
|
"label": "Crop",
|
||||||
|
"link_to": "Crop",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "List",
|
||||||
|
"label": "Pest",
|
||||||
|
"link_to": "Pest",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "List",
|
||||||
|
"label": "Customer",
|
||||||
|
"link_to": "Customer",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "List",
|
||||||
|
"label": "Chemical",
|
||||||
|
"link_to": "Chemical",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "List",
|
||||||
|
"label": "Tray Type",
|
||||||
|
"link_to": "Tray Type",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "List",
|
||||||
|
"label": "Resource Consumption Log",
|
||||||
|
"link_to": "Resource Consumption Log",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "",
|
||||||
|
"label": "Daily Resource Usage Summary",
|
||||||
|
"link_to": "Daily Resource Usage Summary",
|
||||||
|
"report_ref_doctype": "Resource Consumption Log",
|
||||||
|
"type": "Report"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Agri Seedling"
|
"title": "Agri Seedling"
|
||||||
|
|||||||
@@ -132,6 +132,15 @@ has_permission = {
|
|||||||
# "Field Worker" role; see agri_seedling/utils/auth.py
|
# "Field Worker" role; see agri_seedling/utils/auth.py
|
||||||
on_session_creation = "agri_seedling.agri_seedling.utils.auth.set_field_worker_session_expiry"
|
on_session_creation = "agri_seedling.agri_seedling.utils.auth.set_field_worker_session_expiry"
|
||||||
|
|
||||||
|
|
||||||
|
# Before Request — X-Worker-Token auth hook for mobile field-worker app
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Validates the X-Worker-Token header on every request and upgrades the session
|
||||||
|
# from Guest to the proxy user so whitelisted API methods can execute.
|
||||||
|
before_request = [
|
||||||
|
"agri_seedling.agri_seedling.auth_hooks.authenticate_worker_token",
|
||||||
|
]
|
||||||
|
|
||||||
# Website Route Rules (kept minimal — mobile app is the field-worker client)
|
# Website Route Rules (kept minimal — mobile app is the field-worker client)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
website_route_rules = []
|
website_route_rules = []
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
qrcode[pil]
|
||||||
@@ -1 +1,2 @@
|
|||||||
frappe
|
frappe
|
||||||
|
pyjwt
|
||||||
|
|||||||
Reference in New Issue
Block a user