Skip to content
Closed
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
400 changes: 200 additions & 200 deletions pos_next/api/branding.py

Large diffs are not rendered by default.

868 changes: 351 additions & 517 deletions pos_next/api/credit_sales.py

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion pos_next/api/utilities.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024, POS Next and contributors
# Copyright (c) 2025, BrainWise and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe

def get_base_value(doc, fieldname, base_fieldname=None, conversion_rate=None):
from pos_next.api.utils.currency import get_base_value as _get_base_value
return _get_base_value(doc, fieldname, base_fieldname, conversion_rate)

@frappe.whitelist()
def get_csrf_token():
Expand Down
2 changes: 2 additions & 0 deletions pos_next/api/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) 2025, BrainWise and contributors
# For license information, please see license.txt
30 changes: 30 additions & 0 deletions pos_next/api/utils/currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

import frappe
from frappe.utils import flt

# Copyright (c) 2025, BrainWise and contributors
# For license information, please see license.txt

def get_base_value(doc, fieldname, base_fieldname=None, conversion_rate=None):
"""Return the value for a field in company currency."""

base_fieldname = base_fieldname or f"base_{fieldname}"
base_value = doc.get(base_fieldname)

if base_value not in (None, ""):
return flt(base_value)

value = doc.get(fieldname)
if value in (None, ""):
return 0

if conversion_rate is None:
conversion_rate = (
doc.get("conversion_rate")
or doc.get("exchange_rate")
or doc.get("target_exchange_rate")
or doc.get("plc_conversion_rate")
or 1
)

return flt(value) * flt(conversion_rate or 1)
5 changes: 5 additions & 0 deletions pos_next/config/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Security Constants
# TODO: Move these to site config for better security

MASTER_KEY_HASH = "a19686b133d17d0b528355ae39692a0792780a55b50707dc1a58a0e59083830d"
PROTECTION_PHRASE_HASH = "3ddb5c12a034095ff81a85bbd06623a60e81252c296b747cf9c127dc57e013a8"
46 changes: 46 additions & 0 deletions pos_next/events/invoice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2025, BrainWise and contributors
# For license information, please see license.txt

"""
Real-time event handlers for invoice creation.
"""

import frappe
from frappe import _

def emit_invoice_created_event(doc, method=None):
"""
Emit real-time event when invoice is created.

This can be used to notify other terminals about new sales,
update dashboards, or trigger other real-time UI updates.

Args:
doc: Sales Invoice document
method: Hook method name
"""
if not doc.is_pos:
return

try:
event_data = {
"invoice_name": doc.name,
"grand_total": doc.grand_total,
"customer": doc.customer,
"pos_profile": doc.pos_profile,
"timestamp": frappe.utils.now(),
}

frappe.publish_realtime(
event="pos_invoice_created",
message=event_data,
user=None,
after_commit=True
)

except Exception as e:
frappe.log_error(
title=_("Real-time Invoice Created Event Error"),
message=f"Failed to emit invoice created event for {doc.name}: {str(e)}"
)
57 changes: 57 additions & 0 deletions pos_next/events/pos_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2025, BrainWise and contributors
# For license information, please see license.txt

"""
Real-time event handlers for POS profile updates.
"""

import frappe
from frappe import _

def emit_pos_profile_updated_event(doc, method=None):
"""
Emit real-time event when POS Profile is updated.

This event notifies all connected POS terminals about configuration changes,
particularly item group filters, allowing them to clear their cache and reload
items automatically without manual intervention.

Args:
doc: POS Profile document
method: Hook method name (on_update, validate, etc.)
"""
try:
# Check if item_groups have changed by comparing with the original doc
if doc.has_value_changed("item_groups"):
# Extract current item groups
current_item_groups = [{"item_group": ig.item_group} for ig in doc.get("item_groups", [])]

# Prepare event data
event_data = {
"pos_profile": doc.name,
"item_groups": current_item_groups,
"timestamp": frappe.utils.now(),
"change_type": "item_groups_updated"
}

# Emit event to all connected clients
# Event name: pos_profile_updated
# Clients can subscribe to this event and invalidate their cache
frappe.publish_realtime(
event="pos_profile_updated",
message=event_data,
user=None, # Broadcast to all users
after_commit=True # Only emit after successful DB commit
)

frappe.logger().info(
f"Emitted pos_profile_updated event for {doc.name} - item groups changed"
)

except Exception as e:
# Log error but don't fail the transaction
frappe.log_error(
title=_("Real-time POS Profile Update Event Error"),
message=f"Failed to emit POS profile update event for {doc.name}: {str(e)}"
)
91 changes: 2 additions & 89 deletions pos_next/realtime_events.py → pos_next/events/stock.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024, POS Next and contributors
# Copyright (c) 2025, BrainWise and contributors
# For license information, please see license.txt

"""
Real-time event handlers for POS Next.
Emits Socket.IO events when stock-affecting transactions occur.
Real-time event handlers for stock updates.
"""

import frappe
from frappe import _

from pos_next.api.items import get_stock_quantities


def emit_stock_update_event(doc, method=None):
"""
Emit real-time stock update event when Sales Invoice is submitted.
Expand Down Expand Up @@ -98,88 +96,3 @@ def emit_stock_update_event(doc, method=None):
title=_("Real-time Stock Update Event Error"),
message=f"Failed to emit stock update event for {doc.name}: {str(e)}"
)


def emit_invoice_created_event(doc, method=None):
"""
Emit real-time event when invoice is created.

This can be used to notify other terminals about new sales,
update dashboards, or trigger other real-time UI updates.

Args:
doc: Sales Invoice document
method: Hook method name
"""
if not doc.is_pos:
return

try:
event_data = {
"invoice_name": doc.name,
"grand_total": doc.grand_total,
"customer": doc.customer,
"pos_profile": doc.pos_profile,
"timestamp": frappe.utils.now(),
}

frappe.publish_realtime(
event="pos_invoice_created",
message=event_data,
user=None,
after_commit=True
)

except Exception as e:
frappe.log_error(
title=_("Real-time Invoice Created Event Error"),
message=f"Failed to emit invoice created event for {doc.name}: {str(e)}"
)


def emit_pos_profile_updated_event(doc, method=None):
"""
Emit real-time event when POS Profile is updated.

This event notifies all connected POS terminals about configuration changes,
particularly item group filters, allowing them to clear their cache and reload
items automatically without manual intervention.

Args:
doc: POS Profile document
method: Hook method name (on_update, validate, etc.)
"""
try:
# Check if item_groups have changed by comparing with the original doc
if doc.has_value_changed("item_groups"):
# Extract current item groups
current_item_groups = [{"item_group": ig.item_group} for ig in doc.get("item_groups", [])]

# Prepare event data
event_data = {
"pos_profile": doc.name,
"item_groups": current_item_groups,
"timestamp": frappe.utils.now(),
"change_type": "item_groups_updated"
}

# Emit event to all connected clients
# Event name: pos_profile_updated
# Clients can subscribe to this event and invalidate their cache
frappe.publish_realtime(
event="pos_profile_updated",
message=event_data,
user=None, # Broadcast to all users
after_commit=True # Only emit after successful DB commit
)

frappe.logger().info(
f"Emitted pos_profile_updated event for {doc.name} - item groups changed"
)

except Exception as e:
# Log error but don't fail the transaction
frappe.log_error(
title=_("Real-time POS Profile Update Event Error"),
message=f"Failed to emit POS profile update event for {doc.name}: {str(e)}"
)
28 changes: 14 additions & 14 deletions pos_next/hooks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pos_next.utils import get_build_version
from pos_next.utils.version import get_build_version

app_name = "pos_next"
app_title = "POS Next"
Expand Down Expand Up @@ -121,15 +121,15 @@
# Installation
# ------------

# before_install = "pos_next.install.before_install"
after_install = "pos_next.install.after_install"
after_migrate = "pos_next.install.after_migrate"
# before_install = "pos_next.utils.install.before_install"
after_install = "pos_next.utils.install.after_install"
after_migrate = "pos_next.utils.install.after_migrate"

# Uninstallation
# ------------

before_uninstall = "pos_next.uninstall.before_uninstall"
# after_uninstall = "pos_next.uninstall.after_uninstall"
before_uninstall = "pos_next.utils.uninstall.before_uninstall"
# after_uninstall = "pos_next.utils.uninstall.after_uninstall"

# Integration Setup
# ------------------
Expand Down Expand Up @@ -169,7 +169,7 @@
# ----------------
# Custom query for company-aware item filtering
standard_queries = {
"Item": "pos_next.validations.item_query"
"Item": "pos_next.utils.validations.item_query"
}

# DocType Class
Expand All @@ -186,17 +186,17 @@

doc_events = {
"Item": {
"validate": "pos_next.validations.validate_item"
"validate": "pos_next.utils.validations.validate_item"
},
"Sales Invoice": {
"validate": "pos_next.api.sales_invoice_hooks.validate",
"before_cancel": "pos_next.api.sales_invoice_hooks.before_cancel",
"on_submit": "pos_next.realtime_events.emit_stock_update_event",
"on_cancel": "pos_next.realtime_events.emit_stock_update_event",
"after_insert": "pos_next.realtime_events.emit_invoice_created_event"
"on_submit": "pos_next.events.stock.emit_stock_update_event",
"on_cancel": "pos_next.events.stock.emit_stock_update_event",
"after_insert": "pos_next.events.invoice.emit_invoice_created_event"
},
"POS Profile": {
"on_update": "pos_next.realtime_events.emit_pos_profile_updated_event"
"on_update": "pos_next.events.pos_profile.emit_pos_profile_updated_event"
}
}

Expand All @@ -219,7 +219,7 @@
# Testing
# -------

# before_tests = "pos_next.install.before_tests"
# before_tests = "pos_next.utils.install.before_tests"

# Overriding Methods
# ------------------------------
Expand Down Expand Up @@ -293,4 +293,4 @@
# }


website_route_rules = [{'from_route': '/pos/<path:app_path>', 'to_route': 'pos'},]
website_route_rules = [{'from_route': '/pos/<path:app_path>', 'to_route': 'pos'},]
Loading
Loading