Skip to content
Open
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
22 changes: 22 additions & 0 deletions pos_next/pos_next/custom/pos_closing_shift.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"custom_fields": [
{
"allow_on_submit": 1,
"dt": "POS Closing Shift",
"fieldname": "custom_bank_deposit",
"fieldtype": "Link",
"insert_after": "pos_opening_shift",
"label": "Bank Deposit",
"module": "POS Next",
"name": "POS Closing Shift-custom_bank_deposit",
"no_copy": 1,
"options": "Bank Deposits",
"read_only": 1
}
],
"custom_perms": [],
"doctype": "POS Closing Shift",
"links": [],
"property_setters": [],
"sync_on_migrate": 1
}
62 changes: 62 additions & 0 deletions pos_next/pos_next/doctype/bank_deposits/bank_deposits.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2026, BrainWise and contributors
// For license information, please see license.txt

function set_pos_closing_shift_query(frm) {
frm.set_query("pos_closing_shift", () => ({
query: "pos_next.pos_next.doctype.bank_deposits.bank_deposits.get_pos_closing_shifts",
filters: {
pos_profile: frm.doc.pos_profile,
},
}));
}

frappe.ui.form.on("Bank Deposits", {
onload(frm) {
if (frm.is_new() && !frm.doc.posting_date) {
frm.set_value("posting_date", frappe.datetime.get_today());
}

frm.set_query("pos_profile", () => ({
filters: { disabled: 0 },
}));

set_pos_closing_shift_query(frm);
},

refresh(frm) {
set_pos_closing_shift_query(frm);
},

pos_profile(frm) {
if (frm.doc.pos_closing_shift) {
frm.set_value("pos_closing_shift", "");
}
},

pos_closing_shift(frm) {
if (!frm.doc.pos_closing_shift) return;

frappe.db.get_value(
"POS Closing Shift",
frm.doc.pos_closing_shift,
["pos_profile", "docstatus"],
(r) => {
if (!r) return;

if (r.docstatus !== 1) {
frappe.msgprint({
title: __("Invalid Shift"),
message: __("Only submitted POS Closing Shifts can be selected."),
indicator: "red",
});
frm.set_value("pos_closing_shift", "");
return;
}

if (r.pos_profile && r.pos_profile !== frm.doc.pos_profile) {
frm.set_value("pos_profile", r.pos_profile);
}
},
);
},
});
165 changes: 165 additions & 0 deletions pos_next/pos_next/doctype/bank_deposits/bank_deposits.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
{
"actions": [],
"allow_rename": 0,
"autoname": "BD-.YYYY.-.#####",
"creation": "2026-06-09 10:00:00.000000",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"pos_profile",
"posting_date",
"column_break_1",
"pos_closing_shift",
"section_break_amount",
"deposit_amount",
"section_break_attachment",
"bank_transaction_doc",
"section_break_remarks",
"remarks",
"amended_from"
],
"fields": [
{
"fieldname": "pos_profile",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "POS Profile",
"options": "POS Profile",
"reqd": 1
},
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Posting Date",
"read_only": 1
},
{
"fieldname": "column_break_1",
"fieldtype": "Column Break"
},
{
"fieldname": "pos_closing_shift",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "POS Closing Shift",
"options": "POS Closing Shift",
"reqd": 1
},
{
"fieldname": "section_break_amount",
"fieldtype": "Section Break",
"label": "Deposit Details"
},
{
"fieldname": "deposit_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Deposit Amount",
"reqd": 1
},
{
"fieldname": "section_break_attachment",
"fieldtype": "Section Break",
"label": "Bank Receipt"
},
{
"fieldname": "bank_transaction_doc",
"fieldtype": "Attach",
"label": "Bank Transaction Document",
"reqd": 1
},
{
"fieldname": "section_break_remarks",
"fieldtype": "Section Break"
},
{
"fieldname": "remarks",
"fieldtype": "Small Text",
"label": "Remarks"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Bank Deposits",
"print_hide": 1,
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2026-06-09 10:00:00.000000",
"modified_by": "Administrator",
"module": "POS Next",
"name": "Bank Deposits",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Nexus POS Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "POSNext Cashier",
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
120 changes: 120 additions & 0 deletions pos_next/pos_next/doctype/bank_deposits/bank_deposits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copyright (c) 2026, BrainWise and contributors
# For license information, please see license.txt

import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import flt, today


class BankDeposits(Document):
def validate(self):
if not self.posting_date:
self.posting_date = today()

self.validate_pos_profile()
self.validate_pos_closing_shift()
self.validate_deposit_amount()
self.validate_bank_receipt()

def validate_pos_profile(self):
if frappe.db.get_value("POS Profile", self.pos_profile, "disabled"):
frappe.throw(
_("POS Profile {0} is not active").format(frappe.bold(self.pos_profile)),
title=_("Invalid POS Profile"),
)

def validate_pos_closing_shift(self):
shift = frappe.db.get_value(
"POS Closing Shift",
self.pos_closing_shift,
["docstatus", "pos_profile", "pos_opening_shift"],
as_dict=True,
)
if not shift:
frappe.throw(_("POS Closing Shift {0} does not exist").format(self.pos_closing_shift))

if shift.docstatus != 1:
frappe.throw(_("POS Closing Shift must be submitted"))

opening_status = frappe.db.get_value("POS Opening Shift", shift.pos_opening_shift, "status")
if opening_status != "Closed":
frappe.throw(_("POS Closing Shift must be closed"))

if shift.pos_profile != self.pos_profile:
frappe.throw(_("POS Closing Shift does not belong to the selected POS Profile"))

existing = frappe.db.exists(
"Bank Deposits",
{
"pos_closing_shift": self.pos_closing_shift,
"name": ["!=", self.name],
"docstatus": ["!=", 2],
},
)
if existing:
frappe.throw(
_("Bank Deposit {0} already exists for this shift").format(frappe.bold(existing)),
title=_("Duplicate Bank Deposit"),
)

def validate_deposit_amount(self):
if flt(self.deposit_amount) <= 0:
frappe.throw(_("Deposit Amount must be greater than zero"))

def validate_bank_receipt(self):
if not self.bank_transaction_doc:
frappe.throw(_("Bank Transaction Document is required"))

def on_submit(self):
frappe.db.set_value(
"POS Closing Shift",
self.pos_closing_shift,
"custom_bank_deposit",
self.name,
update_modified=False,
)

def on_cancel(self):
frappe.db.set_value(
"POS Closing Shift",
self.pos_closing_shift,
"custom_bank_deposit",
None,
update_modified=False,
)


@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_pos_closing_shifts(doctype, txt, searchfield, start, page_len, filters):
"""Return submitted, closed POS Closing Shifts without an existing Bank Deposit."""
filters = filters or {}
pos_profile = filters.get("pos_profile")
profile_condition = "AND pcs.pos_profile = %(pos_profile)s" if pos_profile else ""

return frappe.db.sql(
f"""
SELECT pcs.name
FROM `tabPOS Closing Shift` pcs
INNER JOIN `tabPOS Opening Shift` pos ON pos.name = pcs.pos_opening_shift
WHERE pcs.docstatus = 1
AND pos.status = 'Closed'
AND pcs.name NOT IN (
SELECT bd.pos_closing_shift
FROM `tabBank Deposits` bd
WHERE bd.docstatus = 1
AND bd.pos_closing_shift IS NOT NULL
)
{profile_condition}
AND pcs.name LIKE %(txt)s
ORDER BY pcs.period_end_date DESC
LIMIT %(page_len)s OFFSET %(start)s
""",
{
"txt": f"%{txt}%",
"start": start,
"page_len": page_len,
"pos_profile": pos_profile,
},
)
8 changes: 8 additions & 0 deletions pos_next/pos_next/doctype/bank_deposits/test_bank_deposits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2026, BrainWise and contributors
# For license information, please see license.txt

from frappe.tests import IntegrationTestCase


class TestBankDeposits(IntegrationTestCase):
pass
Loading
Loading