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
88 changes: 0 additions & 88 deletions csf_tz/csf_tz/sales_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,27 +207,6 @@ frappe.ui.form.on("Sales Invoice", {
});

frappe.ui.form.on("Sales Invoice Item", {
item_code: function (frm, cdt, cdn) {
validate_item_remaining_qty(frm, cdt, cdn);
},
qty: function (frm, cdt, cdn) {
validate_item_remaining_qty(frm, cdt, cdn);
},
stock_qty: function (frm, cdt, cdn) {
validate_item_remaining_stock_qty(frm, cdt, cdn);
},
uom: function (frm, cdt, cdn) {
validate_item_remaining_qty(frm, cdt, cdn);
},
allow_over_sell: function (frm, cdt, cdn) {
validate_item_remaining_stock_qty(frm, cdt, cdn);
},
conversion_factor: function (frm, cdt, cdn) {
validate_item_remaining_stock_qty(frm, cdt, cdn);
},
warehouse: function (frm, cdt, cdn) {
validate_item_remaining_stock_qty(frm, cdt, cdn);
},
csf_tz_create_wtax_entry: (frm, cdt, cdn) => {
frappe
.call("csf_tz.custom_api.make_withholding_tax_gl_entries_for_sales", {
Expand All @@ -240,73 +219,6 @@ frappe.ui.form.on("Sales Invoice Item", {
},
});

var validate_item_remaining_qty = function (frm, cdt, cdn) {
const item_row = locals[cdt][cdn];
if (item_row.item_code == null) {
return;
}
if (item_row.allow_over_sell == 1) {
return;
}
const conversion_factor = get_conversion_factor(
item_row,
item_row.item_code,
item_row.uom
);
frappe.call({
method: "csf_tz.custom_api.validate_item_remaining_qty",
args: {
item_code: item_row.item_code,
company: frm.doc.company,
warehouse: item_row.warehouse,
stock_qty: item_row.qty * conversion_factor,
so_detail: item_row.so_detail,
},
async: false,
});
};

var validate_item_remaining_stock_qty = function (frm, cdt, cdn) {
const item_row = locals[cdt][cdn];
if (item_row.item_code == null) {
return;
}
if (item_row.allow_over_sell == 1) {
return;
}
frappe.call({
method: "csf_tz.custom_api.validate_item_remaining_qty",
args: {
item_code: item_row.item_code,
company: frm.doc.company,
warehouse: item_row.warehouse,
stock_qty: item_row.stock_qty,
},
async: false,
});
};

var get_conversion_factor = function (item_row, item_code, uom) {
if (item_code && uom) {
let conversion_factor = 0;
frappe.call({
method: "erpnext.stock.get_item_details.get_conversion_factor",
child: item_row,
args: {
item_code: item_code,
uom: uom,
},
async: false,
callback: function (r) {
if (!r.exc) {
conversion_factor = r.message.conversion_factor;
}
},
});
return conversion_factor;
}
};

frappe.ui.keys.add_shortcut({
shortcut: "ctrl+q",
action: () => {
Expand Down
144 changes: 0 additions & 144 deletions csf_tz/custom_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,150 +472,6 @@ def delete_doc(doctype, docname):
frappe.msgprint(_("{0} {1} is Deleted").format("Stock Entry", doc.name))


def get_pending_si_delivery_item_count(item_code, company, warehouse):
query = """SELECT SUM(SII.delivered_qty) as delivered_count ,SUM(SII.stock_qty) as sold_count
FROM `tabSales Invoice` AS SI
INNER JOIN `tabSales Invoice Item` AS SII ON SI.name = SII.parent
WHERE
SII.item_code = '%s'
AND SII.parent = SI.name
AND SI.docstatus= 1
AND SI.company = '%s'
AND SII.warehouse = '%s'
AND SII.so_detail IS NULL
AND (SII.so_detail IS NOT NULL AND SII.delivery_note IS NOT NULL)
AND SI.update_stock = 0
AND SII.is_ignored_in_pending_qty != 1
AND SII.delivered_qty != SII.stock_qty
""" % (
item_code,
company,
warehouse,
)

counts = frappe.db.sql(query, as_dict=True)
if len(counts) > 0:
if not counts[0]["sold_count"]:
counts[0]["sold_count"] = 0
if not counts[0]["delivered_count"]:
counts[0]["delivered_count"] = 0
return counts[0]["sold_count"] - counts[0]["delivered_count"]


def get_pending_delivery_item_count(item_code, company, warehouse):
query = """ SELECT SUM(SOI.delivered_qty) as delivered_count ,SUM(SOI.stock_qty) as sold_count
FROM `tabSales Order` AS SO
INNER JOIN `tabSales Order Item` AS SOI ON SO.name = SOI.parent
WHERE
SOI.item_code = '%s'
AND SOI.parent = SO.name
AND SO.docstatus= 1
AND SO.company = '%s'
AND SOI.warehouse = '%s'
AND SO.status NOT IN ('Closed', 'On Hold', 'Cancelled', 'Completed')
""" % (
item_code,
company,
warehouse,
)

counts = frappe.db.sql(query, as_dict=True)
if len(counts) > 0:
if not counts[0]["sold_count"]:
counts[0]["sold_count"] = 0
if not counts[0]["delivered_count"]:
counts[0]["delivered_count"] = 0
return counts[0]["sold_count"] - counts[0]["delivered_count"]
else:
return 0


def get_item_balance(item_code, company, warehouse=None):
if company and not warehouse:
warehouse = frappe.get_all("Warehouse", filters={"company": company, "lft": 1}, fields=["name"])[0][
"name"
]
values, condition = [item_code], ""
if warehouse:
lft, rgt, is_group = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt", "is_group"])

if is_group:
values.extend([lft, rgt])
condition += "and exists (\
select name from `tabWarehouse` wh where wh.name = tabBin.warehouse\
and wh.lft >= %s and wh.rgt <= %s)"

else:
values.append(warehouse)
condition += " AND warehouse = %s"

# condition is a constant fragment with %s placeholders; values are bound through Frappe SQL params
# nosemgrep: frappe-sql-format-injection
actual_qty = frappe.db.sql(
"""select sum(actual_qty) from tabBin
where item_code=%s {0}""".format(condition),
values,
)[0][0]

return actual_qty


@frappe.whitelist()
def validate_item_remaining_qty(
item_code: Any, company: Any, warehouse: Any = None, stock_qty: Any = None, so_detail: Any = None
):
if not warehouse or not stock_qty:
return
if frappe.db.get_single_value("Stock Settings", "allow_negative_stock"):
return
is_stock_item = frappe.get_value("Item", item_code, "is_stock_item")
if is_stock_item == 1:
item_balance = get_item_balance(item_code, company, warehouse) or 0
if not item_balance:
frappe.throw(
_("<B>{0}</B> item balance is ZERO. Cannot proceed unless Allow Over Sell").format(item_code)
)
pending_delivery_item_count = get_pending_delivery_item_count(item_code, company, warehouse) or 0
pending_si = get_pending_si_delivery_item_count(item_code, company, warehouse) or 0
# The float(stock_qty) is removed to allow ignore the item itself
if so_detail:
if pending_delivery_item_count > float(stock_qty):
qty_to_reduce = pending_delivery_item_count
else:
qty_to_reduce = float(stock_qty)
else:
qty_to_reduce = pending_delivery_item_count + float(stock_qty)

item_remaining_qty = item_balance - qty_to_reduce - pending_si
if item_remaining_qty < 0:
imbalance_msg = _(
"Item Balance: '{2}'<br>Pending Sales Order: '{3}'<br>Pending Direct Sales Invoice: {5}<br>Current request is {4}<br><b>Results into balance Qty for '{0}' to '{1}'</b>"
).format(
item_code,
item_remaining_qty,
item_balance,
pending_delivery_item_count,
float(stock_qty),
pending_si,
)
if not frappe.db.get_single_value("CSF TZ Settings", "item_qty_poppup_message"):
frappe.msgprint(imbalance_msg, alert=True)
else:
frappe.throw(imbalance_msg)


def validate_items_remaining_qty(doc, method):
for item in doc.items:
if not item.allow_over_sell and not (item.so_detail and item.delivery_note):
validate_item_remaining_qty(
item.item_code,
doc.company,
item.warehouse,
item.stock_qty,
item.so_detail,
)


def on_cancel_fees(doc, method):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries

Expand Down
1 change: 0 additions & 1 deletion csf_tz/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@
],
"validate": [
"csf_tz.custom_api.check_validate_delivery_note",
"csf_tz.custom_api.validate_items_remaining_qty",
"csf_tz.custom_api.calculate_price_reduction",
],
"before_cancel": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,22 +562,6 @@ def execute():
},
],
"Sales Invoice Item":[
{
"allow_on_submit": 1,
"fieldname": "is_ignored_in_pending_qty",
"fieldtype": "Check",
"insert_after": "item_code",
"label": "Is Ignored In Pending Qty",
"permlevel": 2,
"precision": ""
},
{
"default": "0",
"fieldname": "allow_over_sell",
"fieldtype": "Check",
"insert_after": "customer_item_code",
"label": "Allow Over Sell",
},
{
"fetch_from": "item_code.withholding_tax_rate_on_sales",
"fieldname": "withholding_tax_rate",
Expand Down
Loading