Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion employee_self_service/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.2.2"
__version__ = "2.2.3"
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
"column_break_7qksp",
"default_print_format",
"geofencing_based_on",
"selling_settings_section",
"allow_user_to_change_rate",
"column_break_goaq",
"allow_user_to_change_uom",
"column_break_kxpj",
"visit_proof_required",
"section_break_xjas",
"enable_ess_notification",
"check_in_with_image",
Expand Down Expand Up @@ -254,13 +260,45 @@
"fieldname": "show_stock_balance_in_item_list",
"fieldtype": "Check",
"label": "Show Stock Balance in Item List"
},
{
"default": "0",
"description": "When enabled, visit proof (image attachment) is mandatory while creating a Visit from the mobile app.",
"fieldname": "visit_proof_required",
"fieldtype": "Check",
"label": "Visit Proof Required"
},
{
"fieldname": "selling_settings_section",
"fieldtype": "Section Break",
"label": "Selling Settings"
},
{
"fieldname": "column_break_goaq",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_kxpj",
"fieldtype": "Column Break"
},
{
"default": "1",
"fieldname": "allow_user_to_change_rate",
"fieldtype": "Check",
"label": "Allow User to Change Rate"
},
{
"default": "1",
"fieldname": "allow_user_to_change_uom",
"fieldtype": "Check",
"label": "Allow User to Change UOM"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-03-13 17:25:02.606955",
"modified": "2026-04-19 18:19:16.219747",
"modified_by": "Administrator",
"module": "Employee Self Service",
"name": "Employee Self Service Settings",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Copyright (c) 2022, Nesscale Solutions Private Limited and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document

from employee_self_service.setup import disable_geolocation_tracking


class EmployeeSelfServiceSettings(Document):
pass
def on_update(self):
disable_geolocation_tracking()
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"location",
"column_break_ssqu5",
"employee",
"user"
"user",
"section_break_visit_proof",
"visit_proof"
],
"fields": [
{
Expand Down Expand Up @@ -100,6 +102,16 @@
"fieldtype": "Link",
"label": "User",
"options": "User"
},
{
"fieldname": "section_break_visit_proof",
"fieldtype": "Section Break",
"label": "Visit Proof"
},
{
"fieldname": "visit_proof",
"fieldtype": "Attach Image",
"label": "Visit Proof"
}
],
"index_web_pages_for_search": 1,
Expand Down
3 changes: 3 additions & 0 deletions employee_self_service/mobile/v1/ess.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,9 @@ def get_dashboard():
),
"enable_todo": settings.get("enable_todo"),
"enable_modular_menu": settings.get("enable_modular_menu"),
"visit_proof_required": settings.get("visit_proof_required"),
"allow_user_to_change_rate": settings.get("allow_user_to_change_rate"),
"allow_user_to_change_uom": settings.get("allow_user_to_change_uom"),
}
# "approval_requests": get_workflow_documents(internal=True)
dashboard_data["employee_image"] = emp_data.get("image")
Expand Down
129 changes: 127 additions & 2 deletions employee_self_service/mobile/v1/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@
ess_validate,
exception_handler,
gen_response,
get_employee_by_user,
)

# Mapping of document type keys to their doctype name and image field
DOCUMENT_IMAGE_MAP = {
"visit": {
"doctype": "Visit",
"field": "visit_proof",
"employee_field": "employee",
},
}


@frappe.whitelist()
@ess_validate(methods=["POST"])
Expand All @@ -20,16 +30,61 @@ def upload_documents():
file_doc = upload_file()
file_doc.attached_to_doctype = frappe.form_dict.reference_doctype
file_doc.attached_to_name = frappe.form_dict.reference_docname
is_private = frappe.form_dict.get("is_private", "1")
file_doc.is_private = int(is_private)
file_doc.save(ignore_permissions=True)
frappe.db.commit()
return gen_response(200, "file added successfully.")
return gen_response(200, "File uploaded successfully.", {
"name": file_doc.name,
"file_url": file_doc.file_url,
"file_name": file_doc.file_name,
"is_private": file_doc.is_private,
})
else:
return gen_response(500, "Please upload a file for attachment.")
except Exception as e:
frappe.db.rollback()
return exception_handler(e)


@frappe.whitelist()
@ess_validate(methods=["POST"])
def delete_file(file_name):
try:
if not file_name:
return gen_response(500, "Please provide the file name.")
if not frappe.db.exists("File", file_name):
return gen_response(404, "File not found.")
frappe.delete_doc("File", file_name)
return gen_response(200, "File deleted successfully.")
except frappe.PermissionError:
return gen_response(403, "Not permitted to delete this file.")
except Exception as e:
return exception_handler(e)


@frappe.whitelist()
@ess_validate(methods=["GET"])
def get_attachments(reference_doctype, reference_name):
try:
if not reference_doctype:
return gen_response(500, "Please provide a reference document type.")
if not reference_name:
return gen_response(500, "Please provide a reference document name.")
files = frappe.get_all(
"File",
filters={
"attached_to_doctype": reference_doctype,
"attached_to_name": reference_name,
},
fields=["name", "file_name", "file_url", "is_private", "file_size"],
)
return gen_response(200, "Attachments fetched successfully.", files)
except frappe.PermissionError:
return gen_response(403, "Not permitted to view attachments.")
except Exception as e:
return exception_handler(e)


def get_attchment(reference_doctype, reference_name):
return frappe.get_all(
"File",
Expand All @@ -39,3 +94,73 @@ def get_attchment(reference_doctype, reference_name):
},
fields=["*"],
)


@frappe.whitelist()
@ess_validate(methods=["POST"])
def attach_document_image(document_type, document_name):
"""
Generic API to attach an image to a supported document.
Args:
document_type: Key from DOCUMENT_IMAGE_MAP (e.g. "checkin", "visit")
document_name: The document ID (e.g. "HR-CHK-00001", "VISIT00001")
"""
try:
# Validate document_type
doc_config = DOCUMENT_IMAGE_MAP.get(document_type)
if not doc_config:
return gen_response(
400,
f"Unsupported document type '{document_type}'. Supported types: {', '.join(DOCUMENT_IMAGE_MAP.keys())}",
)

doctype = doc_config["doctype"]
image_field = doc_config["field"]
employee_field = doc_config["employee_field"]

# Validate document exists
if not frappe.db.exists(doctype, document_name):
return gen_response(404, f"{doctype} '{document_name}' not found")

# Get the document
doc = frappe.get_doc(doctype, document_name)

# Verify the document belongs to the current user's employee
emp_data = get_employee_by_user(frappe.session.user)
if doc.get(employee_field) != emp_data.get("name"):
return gen_response(403, "You don't have permission to attach image to this document")

# Check if file is in request
if "file" not in frappe.request.files:
return gen_response(400, "No file provided")

# Delete old image if exists
old_file_url = doc.get(image_field)
if old_file_url:
old_file_doc = frappe.db.get_value("File", {"file_url": old_file_url}, "name")
if old_file_doc:
frappe.delete_doc("File", old_file_doc, ignore_permissions=True)

# Upload new file
file = upload_file()
file.attached_to_doctype = doctype
file.attached_to_name = doc.name
file.attached_to_field = image_field
file.save(ignore_permissions=True)

# Update document with image URL
doc.set(image_field, file.get("file_url"))
doc.save(ignore_permissions=True)

return gen_response(
200,
"Image attached successfully",
{
"document_type": document_type,
"document_name": doc.name,
"file_url": file.get("file_url"),
"file_name": file.get("file_name"),
},
)
except Exception as e:
return exception_handler(e)
50 changes: 27 additions & 23 deletions employee_self_service/mobile/v1/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,14 @@ def get_item_list(
filters.append(["Item", "show_in_mobile", "=", 1])
item_list = frappe.get_list(
"Item",
fields=["name", "item_name", "item_code", "image", "stock_uom"],
fields=["name", "item_name", "item_code", "image", "sales_uom", "stock_uom"],
filters=filters,
start=start,
or_filters=or_filters,
page_length=page_length,
)
for item in item_list:
item["uom"] = item.get("sales_uom") or item.get("stock_uom")
if for_filters:
items = item_list
else:
Expand All @@ -355,20 +357,11 @@ def get_items_rate(items, customer=None, warehouse=None):
show_stock_balance = ess_settings.get("show_stock_balance_in_item_list", 0)

for item in items:
item_price = frappe.get_all(
"Item Price",
filters={
"item_code": item.name,
"price_list": price_list,
"uom": item.stock_uom,
},
fields=["price_list_rate"],
order_by="valid_from desc",
)
uom = item.get("uom")
item_price = _get_item_price(
item_code=item.name,
price_list=price_list,
uom=item.stock_uom,
uom=uom,
)
item_price_currency = fmt_money(
item_price if item_price else 0.0,
Expand Down Expand Up @@ -412,26 +405,28 @@ def get_uoms(customer, item):
global_defaults = get_global_defaults()
sales_price_list = get_default_price_list(customer=customer)
item_doc = frappe.get_doc("Item", item)
default_uom = item_doc.get("sales_uom") or item_doc.get("stock_uom")
stock_uom = item_doc.get("stock_uom")
uoms = []
default_uom_price = _get_item_price(
item_code=item_doc.get("name"),
price_list=sales_price_list,
uom=item_doc.get("stock_uom"),
uom=default_uom,
)
for uom_row in item_doc.get("uoms"):
if uom_row.get("uom") == item_doc.get("stock_uom"):
uom_hint = f"{uom_row.get('uom')} is default uom"
else:
uom_hint = f"1 {uom_row.get('uom')} = {uom_row.get('conversion_factor')} {item_doc.get('stock_uom')}"
uom_hint = f"1 {uom_row.get('uom')} = {uom_row.get('conversion_factor')} {stock_uom}"
uom_details = dict(
uom=uom_row.get("uom"),
conversion_factor=uom_row.get("conversion_factor"),
uom_hint=uom_hint,
is_default=uom_row.get("uom") == default_uom,
)
get_uom_item_price(
sales_price_list, item, uom_details, default_uom_price, global_defaults
)
uoms.append(uom_details)
# Put sales_uom first in the list
uoms.sort(key=lambda u: u["uom"] != default_uom)
return gen_response(200, "uom details get successfully", uoms)
except frappe.PermissionError:
return gen_response(500, "Not permitted for item")
Expand Down Expand Up @@ -492,8 +487,10 @@ def scan_item(barcode):
item_list = frappe.get_list(
"Item",
filters={"name": item_details.get("item_code")},
fields=["name", "item_name", "item_code", "image", "stock_uom"],
fields=["name", "item_name", "item_code", "image", "sales_uom", "stock_uom"],
)
for item in item_list:
item["uom"] = item.get("sales_uom") or item.get("stock_uom")
items = get_items_rate(item_list)
if len(items) >= 1:
gen_response(200, "Item list get successfully", items[0])
Expand Down Expand Up @@ -618,11 +615,6 @@ def _create_update_order(data, sales_order_doc, default_warehouse):
enable_target_management = frappe.db.get_single_value(
"ESS Target Settings", "enable_target_management"
)
if enable_target_management:
sales_persons = get_sales_person_by_customer(data.get("customer"))
if sales_persons:
sales_order_doc.set("sales_team", sales_persons)

delivery_date = data.get("delivery_date")
for item in data.get("items"):
item["delivery_date"] = delivery_date
Expand All @@ -631,6 +623,18 @@ def _create_update_order(data, sales_order_doc, default_warehouse):
sales_order_doc.update(data)
sales_order_doc.run_method("set_missing_values")
sales_order_doc.run_method("calculate_taxes_and_totals")

# Populate sales team from Customer if not already set
if not sales_order_doc.get("sales_team"):
sales_persons = get_sales_person_by_customer(data.get("customer"))
if sales_persons:
sales_order_doc.set("sales_team", sales_persons)

if enable_target_management:
sales_persons = get_sales_person_by_customer(data.get("customer"))
if sales_persons:
sales_order_doc.set("sales_team", sales_persons)

sales_order_doc.save()


Expand Down
1 change: 1 addition & 0 deletions employee_self_service/mobile/v1/visit.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def get_visit_list():
"time_format(time, '%h:%i:%s') as time",
"visit_type",
"description",
"visit_proof",
],
)
return gen_response(200, "Visit list get successfully", visit_list)
Expand Down
Loading
Loading