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,
|
||||
W191,
|
||||
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
|
||||
exclude=.github/helper/semgrep_rules
|
||||
|
@ -264,7 +264,6 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"fieldname": "is_return",
|
||||
"fieldtype": "Check",
|
||||
@ -1573,7 +1572,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-10-05 12:11:53.871828",
|
||||
"modified": "2022-03-22 13:00:24.166684",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
@ -1623,6 +1622,7 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"timeline_field": "customer",
|
||||
"title_field": "title",
|
||||
"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.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):
|
||||
@ -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"))
|
||||
|
||||
def validate_delivered_serial_nos(self, item):
|
||||
serial_nos = get_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')
|
||||
delivered_serial_nos = get_delivered_serial_nos(item.serial_no)
|
||||
|
||||
if 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)
|
||||
|
||||
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
|
||||
|
||||
if self.is_return or self.docstatus != 1:
|
||||
return
|
||||
for d in self.get('items'):
|
||||
is_service_item = not (frappe.db.get_value('Item', d.get('item_code'), 'is_stock_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)
|
||||
|
||||
si.get("items")[0].serial_no = serial_nos[0]
|
||||
si.update_stock = 1
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
@ -610,6 +611,78 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos_inv.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):
|
||||
args = frappe._dict(args)
|
||||
|
@ -53,7 +53,7 @@ class POSInvoiceMergeLog(Document):
|
||||
frappe.throw(msg)
|
||||
|
||||
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]
|
||||
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)
|
||||
|
||||
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.cancel_linked_invoices()
|
||||
@ -254,7 +254,7 @@ def get_all_unconsolidated_invoices():
|
||||
'docstatus': 1
|
||||
}
|
||||
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
|
||||
|
||||
@ -294,17 +294,62 @@ def unconsolidate_pos_invoices(closing_entry):
|
||||
else:
|
||||
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):
|
||||
try:
|
||||
for customer, invoices in invoice_by_customer.items():
|
||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||
merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
|
||||
for _invoices in split_invoices(invoices):
|
||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||
merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
|
||||
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.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
merge_log.set('pos_invoices', _invoices)
|
||||
merge_log.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
|
||||
if closing_entry:
|
||||
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)
|
||||
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:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
@ -9,7 +9,9 @@
|
||||
"posting_date",
|
||||
"column_break_3",
|
||||
"customer",
|
||||
"grand_total"
|
||||
"grand_total",
|
||||
"is_return",
|
||||
"return_against"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -48,11 +50,27 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:08:42.194979",
|
||||
"modified": "2022-03-24 13:32:02.366257",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Reference",
|
||||
@ -61,5 +79,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
"hide_images",
|
||||
"hide_unavailable_items",
|
||||
"auto_add_item_to_cart",
|
||||
"validate_stock_on_save",
|
||||
"column_break_16",
|
||||
"update_stock",
|
||||
"ignore_pricing_rule",
|
||||
@ -351,6 +352,12 @@
|
||||
{
|
||||
"fieldname": "column_break_25",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "validate_stock_on_save",
|
||||
"fieldtype": "Check",
|
||||
"label": "Validate Stock on Save"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -378,10 +385,11 @@
|
||||
"link_fieldname": "pos_profile"
|
||||
}
|
||||
],
|
||||
"modified": "2021-10-14 14:17:00.469298",
|
||||
"modified": "2022-03-21 13:29:28.480533",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@ -404,5 +412,6 @@
|
||||
}
|
||||
],
|
||||
"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.update_employee_advance_status
|
||||
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
|
||||
|
@ -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 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):
|
||||
@ -275,3 +275,16 @@ def set_customer_info(fieldname, customer, value=""):
|
||||
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
|
||||
frappe.db.set_value('Customer', customer, 'mobile_no', value)
|
||||
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;
|
||||
});
|
||||
|
||||
frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
|
||||
Object.assign(this.settings, profile);
|
||||
this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
|
||||
this.make_app();
|
||||
frappe.call({
|
||||
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_profile_data",
|
||||
args: { "pos_profile": this.pos_profile },
|
||||
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'))
|
||||
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);
|
||||
}
|
||||
|
||||
@ -704,7 +709,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
frappe.dom.freeze();
|
||||
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(() => {
|
||||
frappe.model.clear_doc(doctype, name);
|
||||
this.update_cart_html(current_item, true);
|
||||
@ -715,7 +720,14 @@ erpnext.PointOfSale.Controller = class {
|
||||
}
|
||||
|
||||
async save_and_checkout() {
|
||||
this.frm.is_dirty() && await this.frm.save();
|
||||
this.payment.checkout();
|
||||
if (this.frm.is_dirty()) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
toggle_item_details_section(item) {
|
||||
async toggle_item_details_section(item) {
|
||||
const current_item_changed = !this.compare_with_current_item(item);
|
||||
|
||||
// if item is null or highlighted cart item is clicked twice
|
||||
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.toggle_component(!hide_item_details);
|
||||
|
||||
@ -83,7 +89,6 @@ erpnext.PointOfSale.ItemDetails = class {
|
||||
this.render_form(item);
|
||||
this.events.highlight_cart_item(item);
|
||||
} else {
|
||||
this.validate_serial_batch_item();
|
||||
this.current_item = {};
|
||||
}
|
||||
}
|
||||
@ -103,11 +108,11 @@ erpnext.PointOfSale.ItemDetails = class {
|
||||
(serialized && batched && (no_batch_selected || no_serial_selected))) {
|
||||
|
||||
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'
|
||||
});
|
||||
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) => {
|
||||
if (!frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
|
||||
frappe.run_serially([
|
||||
() => frm.doc.ignore_pricing_rule=1,
|
||||
() => frm.trigger('ignore_pricing_rule'),
|
||||
() => frm.doc.ignore_pricing_rule=0,
|
||||
() => frm.trigger('apply_pricing_rule'),
|
||||
() => frm.save(),
|
||||
() => this.update_totals_section(frm.doc)
|
||||
]);
|
||||
} else if (frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
|
||||
frappe.show_alert({
|
||||
message: __("Ignore Pricing Rule is enabled. Cannot apply coupon code."),
|
||||
indicator: "orange"
|
||||
});
|
||||
if (frm.doc.coupon_code && !frm.applying_pos_coupon_code) {
|
||||
if (!frm.doc.ignore_pricing_rule) {
|
||||
frm.applying_pos_coupon_code = true;
|
||||
frappe.run_serially([
|
||||
() => frm.doc.ignore_pricing_rule=1,
|
||||
() => frm.trigger('ignore_pricing_rule'),
|
||||
() => frm.doc.ignore_pricing_rule=0,
|
||||
() => frm.trigger('apply_pricing_rule'),
|
||||
() => frm.save(),
|
||||
() => this.update_totals_section(frm.doc),
|
||||
() => (frm.applying_pos_coupon_code = false)
|
||||
]);
|
||||
} 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])
|
||||
|
||||
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()
|
||||
def get_pos_reserved_serial_nos(filters):
|
||||
if isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
|
||||
pos_transacted_sr_nos = frappe.db.sql("""select item.serial_no as serial_no
|
||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` item
|
||||
where p.name = item.parent
|
||||
and p.consolidated_invoice is NULL
|
||||
and p.docstatus = 1
|
||||
and item.docstatus = 1
|
||||
and item.item_code = %(item_code)s
|
||||
and item.warehouse = %(warehouse)s
|
||||
and item.serial_no is NOT NULL and item.serial_no != ''
|
||||
""", filters, as_dict=1)
|
||||
POSInvoice = frappe.qb.DocType("POS Invoice")
|
||||
POSInvoiceItem = frappe.qb.DocType("POS Invoice Item")
|
||||
query = frappe.qb.from_(
|
||||
POSInvoice
|
||||
).from_(
|
||||
POSInvoiceItem
|
||||
).select(
|
||||
POSInvoice.is_return,
|
||||
POSInvoiceItem.serial_no
|
||||
).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 = []
|
||||
returned_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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user