Merge pull request #30324 from nextchamp-saqib/fix-pos-issues-again
fix: multiple pos issues
This commit is contained in:
commit
33863cd6ab
2
.flake8
2
.flake8
@ -29,6 +29,8 @@ ignore =
|
|||||||
B950,
|
B950,
|
||||||
W191,
|
W191,
|
||||||
E124, # closing bracket, irritating while writing QB code
|
E124, # closing bracket, irritating while writing QB code
|
||||||
|
E131, # continuation line unaligned for hanging indent
|
||||||
|
E123, # closing bracket does not match indentation of opening bracket's line
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
exclude=.github/helper/semgrep_rules
|
exclude=.github/helper/semgrep_rules
|
||||||
|
@ -264,7 +264,6 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "is_return",
|
"fieldname": "is_return",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@ -1573,7 +1572,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-10-05 12:11:53.871828",
|
"modified": "2022-03-22 13:00:24.166684",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
@ -1623,6 +1622,7 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"timeline_field": "customer",
|
"timeline_field": "customer",
|
||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
|
@ -16,7 +16,11 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
|||||||
)
|
)
|
||||||
from erpnext.accounts.party import get_due_date, get_party_account
|
from erpnext.accounts.party import get_due_date, get_party_account
|
||||||
from erpnext.stock.doctype.batch.batch import get_batch_qty, get_pos_reserved_batch_qty
|
from erpnext.stock.doctype.batch.batch import get_batch_qty, get_pos_reserved_batch_qty
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import (
|
||||||
|
get_delivered_serial_nos,
|
||||||
|
get_pos_reserved_serial_nos,
|
||||||
|
get_serial_nos,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class POSInvoice(SalesInvoice):
|
class POSInvoice(SalesInvoice):
|
||||||
@ -145,12 +149,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
.format(item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed), title=_("Item Unavailable"))
|
.format(item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed), title=_("Item Unavailable"))
|
||||||
|
|
||||||
def validate_delivered_serial_nos(self, item):
|
def validate_delivered_serial_nos(self, item):
|
||||||
serial_nos = get_serial_nos(item.serial_no)
|
delivered_serial_nos = get_delivered_serial_nos(item.serial_no)
|
||||||
delivered_serial_nos = frappe.db.get_list('Serial No', {
|
|
||||||
'item_code': item.item_code,
|
|
||||||
'name': ['in', serial_nos],
|
|
||||||
'sales_invoice': ['is', 'set']
|
|
||||||
}, pluck='name')
|
|
||||||
|
|
||||||
if delivered_serial_nos:
|
if delivered_serial_nos:
|
||||||
bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos))
|
bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos))
|
||||||
@ -172,10 +171,14 @@ class POSInvoice(SalesInvoice):
|
|||||||
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
||||||
|
|
||||||
def validate_stock_availablility(self):
|
def validate_stock_availablility(self):
|
||||||
|
if self.is_return:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.docstatus.is_draft() and not frappe.db.get_value('POS Profile', self.pos_profile, 'validate_stock_on_save'):
|
||||||
|
return
|
||||||
|
|
||||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||||
|
|
||||||
if self.is_return or self.docstatus != 1:
|
|
||||||
return
|
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
is_service_item = not (frappe.db.get_value('Item', d.get('item_code'), 'is_stock_item'))
|
is_service_item = not (frappe.db.get_value('Item', d.get('item_code'), 'is_stock_item'))
|
||||||
if is_service_item:
|
if is_service_item:
|
||||||
|
@ -340,6 +340,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||||
|
|
||||||
si.get("items")[0].serial_no = serial_nos[0]
|
si.get("items")[0].serial_no = serial_nos[0]
|
||||||
|
si.update_stock = 1
|
||||||
si.insert()
|
si.insert()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
@ -610,6 +611,78 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
pos_inv.delete()
|
pos_inv.delete()
|
||||||
pr.delete()
|
pr.delete()
|
||||||
|
|
||||||
|
def test_delivered_serial_no_case(self):
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
|
||||||
|
init_user_and_profile,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
|
|
||||||
|
frappe.db.savepoint('before_test_delivered_serial_no_case')
|
||||||
|
try:
|
||||||
|
se = make_serialized_item()
|
||||||
|
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||||
|
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code="_Test Serialized Item With Series", serial_no=serial_no
|
||||||
|
)
|
||||||
|
|
||||||
|
delivery_document_no = frappe.db.get_value("Serial No", serial_no, "delivery_document_no")
|
||||||
|
self.assertEquals(delivery_document_no, dn.name)
|
||||||
|
|
||||||
|
init_user_and_profile()
|
||||||
|
|
||||||
|
pos_inv = create_pos_invoice(
|
||||||
|
item_code="_Test Serialized Item With Series",
|
||||||
|
serial_no=serial_no,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
do_not_submit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
frappe.db.rollback(save_point='before_test_delivered_serial_no_case')
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
def test_returned_serial_no_case(self):
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
|
||||||
|
init_user_and_profile,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
|
||||||
|
from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
|
|
||||||
|
frappe.db.savepoint('before_test_returned_serial_no_case')
|
||||||
|
try:
|
||||||
|
se = make_serialized_item()
|
||||||
|
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||||
|
|
||||||
|
init_user_and_profile()
|
||||||
|
|
||||||
|
pos_inv = create_pos_invoice(
|
||||||
|
item_code="_Test Serialized Item With Series",
|
||||||
|
serial_no=serial_no,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
pos_return = make_sales_return(pos_inv.name)
|
||||||
|
pos_return.flags.ignore_validate = True
|
||||||
|
pos_return.insert()
|
||||||
|
pos_return.submit()
|
||||||
|
|
||||||
|
pos_reserved_serial_nos = get_pos_reserved_serial_nos({
|
||||||
|
'item_code': '_Test Serialized Item With Series',
|
||||||
|
'warehouse': '_Test Warehouse - _TC'
|
||||||
|
})
|
||||||
|
self.assertTrue(serial_no not in pos_reserved_serial_nos)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
frappe.db.rollback(save_point='before_test_returned_serial_no_case')
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
def create_pos_invoice(**args):
|
def create_pos_invoice(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
@ -53,7 +53,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
|
|
||||||
returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
|
returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
|
||||||
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
||||||
@ -70,7 +70,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
|
|
||||||
self.update_pos_invoices(pos_invoice_docs)
|
self.update_pos_invoices(pos_invoice_docs)
|
||||||
self.cancel_linked_invoices()
|
self.cancel_linked_invoices()
|
||||||
@ -254,7 +254,7 @@ def get_all_unconsolidated_invoices():
|
|||||||
'docstatus': 1
|
'docstatus': 1
|
||||||
}
|
}
|
||||||
pos_invoices = frappe.db.get_all('POS Invoice', filters=filters,
|
pos_invoices = frappe.db.get_all('POS Invoice', filters=filters,
|
||||||
fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer'])
|
fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer', 'is_return', 'return_against'])
|
||||||
|
|
||||||
return pos_invoices
|
return pos_invoices
|
||||||
|
|
||||||
@ -294,17 +294,62 @@ def unconsolidate_pos_invoices(closing_entry):
|
|||||||
else:
|
else:
|
||||||
cancel_merge_logs(merge_logs, closing_entry)
|
cancel_merge_logs(merge_logs, closing_entry)
|
||||||
|
|
||||||
|
def split_invoices(invoices):
|
||||||
|
'''
|
||||||
|
Splits invoices into multiple groups
|
||||||
|
Use-case:
|
||||||
|
If a serial no is sold and later it is returned
|
||||||
|
then split the invoices such that the selling entry is merged first and then the return entry
|
||||||
|
'''
|
||||||
|
# Input
|
||||||
|
# invoices = [
|
||||||
|
# {'pos_invoice': 'Invoice with SR#1 & SR#2', 'is_return': 0},
|
||||||
|
# {'pos_invoice': 'Invoice with SR#1', 'is_return': 1},
|
||||||
|
# {'pos_invoice': 'Invoice with SR#2', 'is_return': 0}
|
||||||
|
# ]
|
||||||
|
# Output
|
||||||
|
# _invoices = [
|
||||||
|
# [{'pos_invoice': 'Invoice with SR#1 & SR#2', 'is_return': 0}],
|
||||||
|
# [{'pos_invoice': 'Invoice with SR#1', 'is_return': 1}, {'pos_invoice': 'Invoice with SR#2', 'is_return': 0}],
|
||||||
|
# ]
|
||||||
|
|
||||||
|
_invoices = []
|
||||||
|
special_invoices = []
|
||||||
|
pos_return_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in invoices if d.is_return and d.return_against]
|
||||||
|
for pos_invoice in pos_return_docs:
|
||||||
|
for item in pos_invoice.items:
|
||||||
|
if not item.serial_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return_against_is_added = any(d for d in _invoices if d.pos_invoice == pos_invoice.return_against)
|
||||||
|
if return_against_is_added:
|
||||||
|
break
|
||||||
|
|
||||||
|
return_against_is_consolidated = frappe.db.get_value('POS Invoice', pos_invoice.return_against, 'status', cache=True) == 'Consolidated'
|
||||||
|
if return_against_is_consolidated:
|
||||||
|
break
|
||||||
|
|
||||||
|
pos_invoice_row = [d for d in invoices if d.pos_invoice == pos_invoice.return_against]
|
||||||
|
_invoices.append(pos_invoice_row)
|
||||||
|
special_invoices.append(pos_invoice.return_against)
|
||||||
|
break
|
||||||
|
|
||||||
|
_invoices.append([d for d in invoices if d.pos_invoice not in special_invoices])
|
||||||
|
|
||||||
|
return _invoices
|
||||||
|
|
||||||
def create_merge_logs(invoice_by_customer, closing_entry=None):
|
def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||||
try:
|
try:
|
||||||
for customer, invoices in invoice_by_customer.items():
|
for customer, invoices in invoice_by_customer.items():
|
||||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
for _invoices in split_invoices(invoices):
|
||||||
merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
|
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||||
merge_log.customer = customer
|
merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
|
||||||
merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
|
merge_log.customer = customer
|
||||||
|
merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
|
||||||
|
|
||||||
merge_log.set('pos_invoices', invoices)
|
merge_log.set('pos_invoices', _invoices)
|
||||||
merge_log.save(ignore_permissions=True)
|
merge_log.save(ignore_permissions=True)
|
||||||
merge_log.submit()
|
merge_log.submit()
|
||||||
|
|
||||||
if closing_entry:
|
if closing_entry:
|
||||||
closing_entry.set_status(update=True, status='Submitted')
|
closing_entry.set_status(update=True, status='Submitted')
|
||||||
|
@ -382,6 +382,68 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
|||||||
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
|
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
|
||||||
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
|
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
def test_serial_no_case_1(self):
|
||||||
|
'''
|
||||||
|
Create a POS Invoice with serial no
|
||||||
|
Create a Return Invoice with serial no
|
||||||
|
Create a POS Invoice with serial no again
|
||||||
|
Consolidate the invoices
|
||||||
|
|
||||||
|
The first POS Invoice should be consolidated with a separate single Merge Log
|
||||||
|
The second and third POS Invoice should be consolidated with a single Merge Log
|
||||||
|
'''
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
|
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
try:
|
||||||
|
se = make_serialized_item()
|
||||||
|
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||||
|
|
||||||
|
init_user_and_profile()
|
||||||
|
|
||||||
|
pos_inv = create_pos_invoice(
|
||||||
|
item_code="_Test Serialized Item With Series",
|
||||||
|
serial_no=serial_no,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
do_not_submit=1
|
||||||
|
)
|
||||||
|
pos_inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 100
|
||||||
|
})
|
||||||
|
pos_inv.submit()
|
||||||
|
|
||||||
|
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||||
|
pos_inv_cn.paid_amount = -100
|
||||||
|
pos_inv_cn.submit()
|
||||||
|
|
||||||
|
pos_inv2 = create_pos_invoice(
|
||||||
|
item_code="_Test Serialized Item With Series",
|
||||||
|
serial_no=serial_no,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
do_not_submit=1
|
||||||
|
)
|
||||||
|
pos_inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 100
|
||||||
|
})
|
||||||
|
pos_inv2.submit()
|
||||||
|
|
||||||
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
|
pos_inv.load_from_db()
|
||||||
|
pos_inv2.load_from_db()
|
||||||
|
|
||||||
|
self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
"posting_date",
|
"posting_date",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"customer",
|
"customer",
|
||||||
"grand_total"
|
"grand_total",
|
||||||
|
"is_return",
|
||||||
|
"return_against"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -48,11 +50,27 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fetch_from": "pos_invoice.is_return",
|
||||||
|
"fieldname": "is_return",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Return",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "pos_invoice.return_against",
|
||||||
|
"fieldname": "return_against",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Return Against",
|
||||||
|
"options": "POS Invoice",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:08:42.194979",
|
"modified": "2022-03-24 13:32:02.366257",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice Reference",
|
"name": "POS Invoice Reference",
|
||||||
@ -61,5 +79,6 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -22,6 +22,7 @@
|
|||||||
"hide_images",
|
"hide_images",
|
||||||
"hide_unavailable_items",
|
"hide_unavailable_items",
|
||||||
"auto_add_item_to_cart",
|
"auto_add_item_to_cart",
|
||||||
|
"validate_stock_on_save",
|
||||||
"column_break_16",
|
"column_break_16",
|
||||||
"update_stock",
|
"update_stock",
|
||||||
"ignore_pricing_rule",
|
"ignore_pricing_rule",
|
||||||
@ -351,6 +352,12 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_25",
|
"fieldname": "column_break_25",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "validate_stock_on_save",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Validate Stock on Save"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@ -378,10 +385,11 @@
|
|||||||
"link_fieldname": "pos_profile"
|
"link_fieldname": "pos_profile"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-10-14 14:17:00.469298",
|
"modified": "2022-03-21 13:29:28.480533",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Profile",
|
"name": "POS Profile",
|
||||||
|
"naming_rule": "Set by user",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -404,5 +412,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
@ -360,4 +360,5 @@ erpnext.patches.v14_0.update_batch_valuation_flag
|
|||||||
erpnext.patches.v14_0.delete_non_profit_doctypes
|
erpnext.patches.v14_0.delete_non_profit_doctypes
|
||||||
erpnext.patches.v14_0.update_employee_advance_status
|
erpnext.patches.v14_0.update_employee_advance_status
|
||||||
erpnext.patches.v13_0.add_cost_center_in_loans
|
erpnext.patches.v13_0.add_cost_center_in_loans
|
||||||
|
erpnext.patches.v13_0.set_return_against_in_pos_invoice_references
|
||||||
erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
|
erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
'''
|
||||||
|
Fetch and Set is_return & return_against from POS Invoice in POS Invoice References table.
|
||||||
|
'''
|
||||||
|
|
||||||
|
POSClosingEntry = frappe.qb.DocType("POS Closing Entry")
|
||||||
|
open_pos_closing_entries = (
|
||||||
|
frappe.qb
|
||||||
|
.from_(POSClosingEntry)
|
||||||
|
.select(POSClosingEntry.name)
|
||||||
|
.where(POSClosingEntry.docstatus == 0)
|
||||||
|
.run(pluck=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not open_pos_closing_entries:
|
||||||
|
return
|
||||||
|
|
||||||
|
POSInvoiceReference = frappe.qb.DocType("POS Invoice Reference")
|
||||||
|
POSInvoice = frappe.qb.DocType("POS Invoice")
|
||||||
|
pos_invoice_references = (
|
||||||
|
frappe.qb
|
||||||
|
.from_(POSInvoiceReference)
|
||||||
|
.join(POSInvoice)
|
||||||
|
.on(POSInvoiceReference.pos_invoice == POSInvoice.name)
|
||||||
|
.select(POSInvoiceReference.name, POSInvoice.is_return, POSInvoice.return_against)
|
||||||
|
.where(POSInvoiceReference.parent.isin(open_pos_closing_entries))
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in pos_invoice_references:
|
||||||
|
frappe.db.set_value("POS Invoice Reference", row.name, "is_return", row.is_return)
|
||||||
|
if row.is_return:
|
||||||
|
frappe.db.set_value("POS Invoice Reference", row.name, "return_against", row.return_against)
|
||||||
|
else:
|
||||||
|
frappe.db.set_value("POS Invoice Reference", row.name, "return_against", None)
|
@ -8,7 +8,7 @@ import frappe
|
|||||||
from frappe.utils.nestedset import get_root_of
|
from frappe.utils.nestedset import get_root_of
|
||||||
|
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability
|
||||||
from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups
|
from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes, get_item_groups
|
||||||
|
|
||||||
|
|
||||||
def search_by_term(search_term, warehouse, price_list):
|
def search_by_term(search_term, warehouse, price_list):
|
||||||
@ -275,3 +275,16 @@ def set_customer_info(fieldname, customer, value=""):
|
|||||||
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
|
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
|
||||||
frappe.db.set_value('Customer', customer, 'mobile_no', value)
|
frappe.db.set_value('Customer', customer, 'mobile_no', value)
|
||||||
contact_doc.save()
|
contact_doc.save()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pos_profile_data(pos_profile):
|
||||||
|
pos_profile = frappe.get_doc('POS Profile', pos_profile)
|
||||||
|
pos_profile = pos_profile.as_dict()
|
||||||
|
|
||||||
|
_customer_groups_with_children = []
|
||||||
|
for row in pos_profile.customer_groups:
|
||||||
|
children = get_child_nodes('Customer Group', row.customer_group)
|
||||||
|
_customer_groups_with_children.extend(children)
|
||||||
|
|
||||||
|
pos_profile.customer_groups = _customer_groups_with_children
|
||||||
|
return pos_profile
|
@ -119,10 +119,15 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
this.allow_negative_stock = flt(message.allow_negative_stock) || false;
|
this.allow_negative_stock = flt(message.allow_negative_stock) || false;
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
|
frappe.call({
|
||||||
Object.assign(this.settings, profile);
|
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_profile_data",
|
||||||
this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
|
args: { "pos_profile": this.pos_profile },
|
||||||
this.make_app();
|
callback: (res) => {
|
||||||
|
const profile = res.message;
|
||||||
|
Object.assign(this.settings, profile);
|
||||||
|
this.settings.customer_groups = profile.customer_groups.map(group => group.name);
|
||||||
|
this.make_app();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,7 +560,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
if (this.item_details.$component.is(':visible'))
|
if (this.item_details.$component.is(':visible'))
|
||||||
this.edit_item_details_of(item_row);
|
this.edit_item_details_of(item_row);
|
||||||
|
|
||||||
if (this.check_serial_batch_selection_needed(item_row))
|
if (this.check_serial_batch_selection_needed(item_row) && !this.item_details.$component.is(':visible'))
|
||||||
this.edit_item_details_of(item_row);
|
this.edit_item_details_of(item_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -704,7 +709,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
frappe.dom.freeze();
|
frappe.dom.freeze();
|
||||||
const { doctype, name, current_item } = this.item_details;
|
const { doctype, name, current_item } = this.item_details;
|
||||||
|
|
||||||
frappe.model.set_value(doctype, name, 'qty', 0)
|
return frappe.model.set_value(doctype, name, 'qty', 0)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
frappe.model.clear_doc(doctype, name);
|
frappe.model.clear_doc(doctype, name);
|
||||||
this.update_cart_html(current_item, true);
|
this.update_cart_html(current_item, true);
|
||||||
@ -715,7 +720,14 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async save_and_checkout() {
|
async save_and_checkout() {
|
||||||
this.frm.is_dirty() && await this.frm.save();
|
if (this.frm.is_dirty()) {
|
||||||
this.payment.checkout();
|
// only move to payment section if save is successful
|
||||||
|
frappe.route_hooks.after_save = () => this.payment.checkout();
|
||||||
|
return this.frm.save(
|
||||||
|
null, null, null, () => this.cart.toggle_checkout_btn(true) // show checkout button on error
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.payment.checkout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -60,12 +60,18 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
return item && item.name == this.current_item.name;
|
return item && item.name == this.current_item.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_item_details_section(item) {
|
async toggle_item_details_section(item) {
|
||||||
const current_item_changed = !this.compare_with_current_item(item);
|
const current_item_changed = !this.compare_with_current_item(item);
|
||||||
|
|
||||||
// if item is null or highlighted cart item is clicked twice
|
// if item is null or highlighted cart item is clicked twice
|
||||||
const hide_item_details = !Boolean(item) || !current_item_changed;
|
const hide_item_details = !Boolean(item) || !current_item_changed;
|
||||||
|
|
||||||
|
if ((!hide_item_details && current_item_changed) || hide_item_details) {
|
||||||
|
// if item details is being closed OR if item details is opened but item is changed
|
||||||
|
// in both cases, if the current item is a serialized item, then validate and remove the item
|
||||||
|
await this.validate_serial_batch_item();
|
||||||
|
}
|
||||||
|
|
||||||
this.events.toggle_item_selector(!hide_item_details);
|
this.events.toggle_item_selector(!hide_item_details);
|
||||||
this.toggle_component(!hide_item_details);
|
this.toggle_component(!hide_item_details);
|
||||||
|
|
||||||
@ -83,7 +89,6 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
this.render_form(item);
|
this.render_form(item);
|
||||||
this.events.highlight_cart_item(item);
|
this.events.highlight_cart_item(item);
|
||||||
} else {
|
} else {
|
||||||
this.validate_serial_batch_item();
|
|
||||||
this.current_item = {};
|
this.current_item = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,11 +108,11 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
(serialized && batched && (no_batch_selected || no_serial_selected))) {
|
(serialized && batched && (no_batch_selected || no_serial_selected))) {
|
||||||
|
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __("Item will be removed since no serial / batch no selected."),
|
message: __("Item is removed since no serial / batch no selected."),
|
||||||
indicator: 'orange'
|
indicator: 'orange'
|
||||||
});
|
});
|
||||||
frappe.utils.play_sound("cancel");
|
frappe.utils.play_sound("cancel");
|
||||||
this.events.remove_item_from_cart();
|
return this.events.remove_item_from_cart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,20 +170,24 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('POS Invoice', 'coupon_code', (frm) => {
|
frappe.ui.form.on('POS Invoice', 'coupon_code', (frm) => {
|
||||||
if (!frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
|
if (frm.doc.coupon_code && !frm.applying_pos_coupon_code) {
|
||||||
frappe.run_serially([
|
if (!frm.doc.ignore_pricing_rule) {
|
||||||
() => frm.doc.ignore_pricing_rule=1,
|
frm.applying_pos_coupon_code = true;
|
||||||
() => frm.trigger('ignore_pricing_rule'),
|
frappe.run_serially([
|
||||||
() => frm.doc.ignore_pricing_rule=0,
|
() => frm.doc.ignore_pricing_rule=1,
|
||||||
() => frm.trigger('apply_pricing_rule'),
|
() => frm.trigger('ignore_pricing_rule'),
|
||||||
() => frm.save(),
|
() => frm.doc.ignore_pricing_rule=0,
|
||||||
() => this.update_totals_section(frm.doc)
|
() => frm.trigger('apply_pricing_rule'),
|
||||||
]);
|
() => frm.save(),
|
||||||
} else if (frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
|
() => this.update_totals_section(frm.doc),
|
||||||
frappe.show_alert({
|
() => (frm.applying_pos_coupon_code = false)
|
||||||
message: __("Ignore Pricing Rule is enabled. Cannot apply coupon code."),
|
]);
|
||||||
indicator: "orange"
|
} else if (frm.doc.ignore_pricing_rule) {
|
||||||
});
|
frappe.show_alert({
|
||||||
|
message: __("Ignore Pricing Rule is enabled. Cannot apply coupon code."),
|
||||||
|
indicator: "orange"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -611,25 +611,60 @@ def auto_fetch_serial_number(
|
|||||||
|
|
||||||
return sorted([d.get('name') for d in serial_numbers])
|
return sorted([d.get('name') for d in serial_numbers])
|
||||||
|
|
||||||
|
def get_delivered_serial_nos(serial_nos):
|
||||||
|
'''
|
||||||
|
Returns serial numbers that delivered from the list of serial numbers
|
||||||
|
'''
|
||||||
|
from frappe.query_builder.functions import Coalesce
|
||||||
|
|
||||||
|
SerialNo = frappe.qb.DocType("Serial No")
|
||||||
|
serial_nos = get_serial_nos(serial_nos)
|
||||||
|
query = frappe.qb.select(SerialNo.name).from_(SerialNo).where(
|
||||||
|
(SerialNo.name.isin(serial_nos))
|
||||||
|
& (Coalesce(SerialNo.delivery_document_type, "") != "")
|
||||||
|
)
|
||||||
|
|
||||||
|
result = query.run()
|
||||||
|
if result and len(result) > 0:
|
||||||
|
delivered_serial_nos = [row[0] for row in result]
|
||||||
|
return delivered_serial_nos
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pos_reserved_serial_nos(filters):
|
def get_pos_reserved_serial_nos(filters):
|
||||||
if isinstance(filters, str):
|
if isinstance(filters, str):
|
||||||
filters = json.loads(filters)
|
filters = json.loads(filters)
|
||||||
|
|
||||||
pos_transacted_sr_nos = frappe.db.sql("""select item.serial_no as serial_no
|
POSInvoice = frappe.qb.DocType("POS Invoice")
|
||||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` item
|
POSInvoiceItem = frappe.qb.DocType("POS Invoice Item")
|
||||||
where p.name = item.parent
|
query = frappe.qb.from_(
|
||||||
and p.consolidated_invoice is NULL
|
POSInvoice
|
||||||
and p.docstatus = 1
|
).from_(
|
||||||
and item.docstatus = 1
|
POSInvoiceItem
|
||||||
and item.item_code = %(item_code)s
|
).select(
|
||||||
and item.warehouse = %(warehouse)s
|
POSInvoice.is_return,
|
||||||
and item.serial_no is NOT NULL and item.serial_no != ''
|
POSInvoiceItem.serial_no
|
||||||
""", filters, as_dict=1)
|
).where(
|
||||||
|
(POSInvoice.name == POSInvoiceItem.parent)
|
||||||
|
& (POSInvoice.docstatus == 1)
|
||||||
|
& (POSInvoiceItem.docstatus == 1)
|
||||||
|
& (POSInvoiceItem.item_code == filters.get('item_code'))
|
||||||
|
& (POSInvoiceItem.warehouse == filters.get('warehouse'))
|
||||||
|
& (POSInvoiceItem.serial_no.isnotnull())
|
||||||
|
& (POSInvoiceItem.serial_no != '')
|
||||||
|
)
|
||||||
|
|
||||||
|
pos_transacted_sr_nos = query.run(as_dict=True)
|
||||||
|
|
||||||
reserved_sr_nos = []
|
reserved_sr_nos = []
|
||||||
|
returned_sr_nos = []
|
||||||
for d in pos_transacted_sr_nos:
|
for d in pos_transacted_sr_nos:
|
||||||
reserved_sr_nos += get_serial_nos(d.serial_no)
|
if d.is_return == 0:
|
||||||
|
reserved_sr_nos += get_serial_nos(d.serial_no)
|
||||||
|
elif d.is_return == 1:
|
||||||
|
returned_sr_nos += get_serial_nos(d.serial_no)
|
||||||
|
|
||||||
|
for sr_no in returned_sr_nos:
|
||||||
|
reserved_sr_nos.remove(sr_no)
|
||||||
|
|
||||||
return reserved_sr_nos
|
return reserved_sr_nos
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user