Project Name: "
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 4575fb544c..0e409fce9c 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -288,7 +288,7 @@ def get_project(doctype, txt, searchfield, start, page_len, filters):
%(mcond)s
{search_condition}
order by name
- limit %(start)s, %(page_len)s""".format(
+ limit %(page_len)s offset %(start)s""".format(
search_columns=search_columns, search_condition=search_cond
),
{
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 2ef966b319..88d5beef17 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -328,7 +328,7 @@ def get_timesheet(doctype, txt, searchfield, start, page_len, filters):
ts.status in ('Submitted', 'Payslip') and tsd.parent = ts.name and
tsd.docstatus = 1 and ts.total_billable_amount > 0
and tsd.parent LIKE %(txt)s {condition}
- order by tsd.parent limit %(start)s, %(page_len)s""".format(
+ order by tsd.parent limit %(page_len)s offset %(start)s""".format(
condition=condition
),
{
@@ -515,7 +515,7 @@ def get_timesheets_list(
tsd.project IN %(projects)s
)
ORDER BY `end_date` ASC
- LIMIT {0}, {1}
+ LIMIT {1} offset {0}
""".format(
limit_start, limit_page_length
),
diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py
index abbbaf5d92..aa955bcc47 100644
--- a/erpnext/projects/report/project_profitability/project_profitability.py
+++ b/erpnext/projects/report/project_profitability/project_profitability.py
@@ -39,17 +39,17 @@ def get_rows(filters):
FROM
(SELECT
si.customer_name,si.base_grand_total,
- si.name as voucher_no,tabTimesheet.employee,
- tabTimesheet.title as employee_name,tabTimesheet.parent_project as project,
- tabTimesheet.start_date,tabTimesheet.end_date,
- tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet,
+ si.name as voucher_no,`tabTimesheet`.employee,
+ `tabTimesheet`.title as employee_name,`tabTimesheet`.parent_project as project,
+ `tabTimesheet`.start_date,`tabTimesheet`.end_date,
+ `tabTimesheet`.total_billed_hours,`tabTimesheet`.name as timesheet,
ss.base_gross_pay,ss.total_working_days,
- tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization
+ `tabTimesheet`.total_billed_hours/(ss.total_working_days * {0}) as utilization
FROM
- `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet
- join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name
- join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled"
- join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(
+ `tabSalary Slip Timesheet` as sst join `tabTimesheet` on `tabTimesheet`.name = sst.time_sheet
+ join `tabSales Invoice Timesheet` as sit on sit.time_sheet = `tabTimesheet`.name
+ join `tabSales Invoice` as si on si.name = sit.parent and si.status != 'Cancelled'
+ join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != 'Cancelled' """.format(
standard_working_hours
)
if conditions:
@@ -72,23 +72,25 @@ def get_conditions(filters):
conditions = []
if filters.get("company"):
- conditions.append("tabTimesheet.company={0}".format(frappe.db.escape(filters.get("company"))))
+ conditions.append("`tabTimesheet`.company={0}".format(frappe.db.escape(filters.get("company"))))
if filters.get("start_date"):
- conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date")))
+ conditions.append("`tabTimesheet`.start_date>='{0}'".format(filters.get("start_date")))
if filters.get("end_date"):
- conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date")))
+ conditions.append("`tabTimesheet`.end_date<='{0}'".format(filters.get("end_date")))
if filters.get("customer_name"):
conditions.append("si.customer_name={0}".format(frappe.db.escape(filters.get("customer_name"))))
if filters.get("employee"):
- conditions.append("tabTimesheet.employee={0}".format(frappe.db.escape(filters.get("employee"))))
+ conditions.append(
+ "`tabTimesheet`.employee={0}".format(frappe.db.escape(filters.get("employee")))
+ )
if filters.get("project"):
conditions.append(
- "tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project")))
+ "`tabTimesheet`.parent_project={0}".format(frappe.db.escape(filters.get("project")))
)
conditions = " and ".join(conditions)
diff --git a/erpnext/projects/utils.py b/erpnext/projects/utils.py
index 000ea66275..3cc4da4f07 100644
--- a/erpnext/projects/utils.py
+++ b/erpnext/projects/utils.py
@@ -25,7 +25,7 @@ def query_task(doctype, txt, searchfield, start, page_len, filters):
case when `%s` like %s then 0 else 1 end,
`%s`,
subject
- limit %s, %s"""
+ limit %s offset %s"""
% (searchfield, "%s", "%s", match_conditions, "%s", searchfield, "%s", searchfield, "%s", "%s"),
- (search_string, search_string, order_by_string, order_by_string, start, page_len),
+ (search_string, search_string, order_by_string, order_by_string, page_len, start),
)
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index de93c82ef2..01f72adf34 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -453,7 +453,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
is_pos: cint(me.frm.doc.is_pos),
is_return: cint(me.frm.doc.is_return),
is_subcontracted: me.frm.doc.is_subcontracted,
- transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,
doctype: me.frm.doc.doctype,
name: me.frm.doc.name,
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index ee48ccb24a..0262469571 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -287,7 +287,7 @@ def get_regional_address_details(party_details, doctype, company):
return party_details
if (
- doctype in ("Sales Invoice", "Delivery Note", "Sales Order")
+ doctype in ("Sales Invoice", "Delivery Note", "Sales Order", "Quotation")
and party_details.company_gstin
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]
) or (
diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
index 5ceb2c0a81..1d4f96b50a 100644
--- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
+++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
@@ -83,7 +83,7 @@ def get_conditions(filters):
("gst_hsn_code", " and gst_hsn_code=%(gst_hsn_code)s"),
("company_gstin", " and company_gstin=%(company_gstin)s"),
("from_date", " and posting_date >= %(from_date)s"),
- ("to_date", "and posting_date <= %(to_date)s"),
+ ("to_date", " and posting_date <= %(to_date)s"),
):
if filters.get(opts[0]):
conditions += opts[1]
diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py
index 92aeb5ee6f..66ade1f89f 100644
--- a/erpnext/regional/report/irs_1099/irs_1099.py
+++ b/erpnext/regional/report/irs_1099/irs_1099.py
@@ -10,7 +10,7 @@ from frappe.utils.data import fmt_money
from frappe.utils.jinja import render_template
from frappe.utils.pdf import get_pdf
from frappe.utils.print_format import read_multi_pdf
-from PyPDF2 import PdfFileWriter
+from PyPDF2 import PdfWriter
from erpnext.accounts.utils import get_fiscal_year
@@ -47,7 +47,7 @@ def execute(filters=None):
s.name = gl.party
AND s.irs_1099 = 1
AND gl.fiscal_year = %(fiscal_year)s
- AND gl.party_type = "Supplier"
+ AND gl.party_type = 'Supplier'
AND gl.company = %(company)s
{conditions}
@@ -106,7 +106,7 @@ def irs_1099_print(filters):
columns, data = execute(filters)
template = frappe.get_doc("Print Format", "IRS 1099 Form").html
- output = PdfFileWriter()
+ output = PdfWriter()
for row in data:
row["fiscal_year"] = fiscal_year
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
index 70f2c0a333..3d486ce650 100644
--- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
@@ -65,7 +65,7 @@ class VATAuditReport(object):
`tab{doctype}`
WHERE
docstatus = 1 {where_conditions}
- and is_opening = "No"
+ and is_opening = 'No'
ORDER BY
posting_date DESC
""".format(
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py
index 575b956686..ac83c0f046 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.py
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.py
@@ -78,7 +78,7 @@ def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
"""select name, item_name, description from tabItem
where is_stock_item=0 and name not in (select name from `tabProduct Bundle`)
- and %s like %s %s limit %s, %s"""
+ and %s like %s %s limit %s offset %s"""
% (searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
- ("%%%s%%" % txt, start, page_len),
+ ("%%%s%%" % txt, page_len, start),
)
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 34e9a52e11..70ae085051 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -20,6 +20,20 @@ frappe.ui.form.on('Quotation', {
frm.set_df_property('packed_items', 'cannot_add_rows', true);
frm.set_df_property('packed_items', 'cannot_delete_rows', true);
+
+ frm.set_query('company_address', function(doc) {
+ if(!doc.company) {
+ frappe.throw(__('Please set Company'));
+ }
+
+ return {
+ query: 'frappe.contacts.doctype.address.address.address_query',
+ filters: {
+ link_doctype: 'Company',
+ link_name: doc.company
+ }
+ };
+ });
},
refresh: function(frm) {
@@ -70,7 +84,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
}
}
- if(doc.docstatus == 1 && doc.status!=='Lost') {
+ if(doc.docstatus == 1 && !(['Lost', 'Ordered']).includes(doc.status)) {
if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
cur_frm.add_custom_button(__('Sales Order'),
cur_frm.cscript['Make Sales Order'], __('Create'));
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index 75443abe49..bb2f95dd17 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -296,7 +296,7 @@
"read_only": 1
},
{
- "depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name",
+ "depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name",
"fieldname": "col_break98",
"fieldtype": "Column Break",
"width": "50%"
@@ -316,7 +316,7 @@
"read_only": 1
},
{
- "depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name",
+ "depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name",
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 1,
@@ -897,7 +897,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Draft\nOpen\nReplied\nOrdered\nLost\nCancelled\nExpired",
+ "options": "Draft\nOpen\nReplied\nPartially Ordered\nOrdered\nLost\nCancelled\nExpired",
"print_hide": 1,
"read_only": 1,
"reqd": 1
@@ -986,7 +986,7 @@
"idx": 82,
"is_submittable": 1,
"links": [],
- "modified": "2022-04-07 11:01:31.157084",
+ "modified": "2022-06-11 20:35:32.635804",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
@@ -1084,4 +1084,4 @@
"states": [],
"timeline_field": "party_name",
"title_field": "title"
-}
\ No newline at end of file
+}
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 548813ddef..4fa4515a0f 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -70,8 +70,32 @@ class Quotation(SellingController):
title=_("Unpublished Item"),
)
- def has_sales_order(self):
- return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1})
+ def get_ordered_status(self):
+ ordered_items = frappe._dict(
+ frappe.db.get_all(
+ "Sales Order Item",
+ {"prevdoc_docname": self.name, "docstatus": 1},
+ ["item_code", "sum(qty)"],
+ group_by="item_code",
+ as_list=1,
+ )
+ )
+
+ status = "Open"
+ if ordered_items:
+ status = "Ordered"
+
+ for item in self.get("items"):
+ if item.qty > ordered_items.get(item.item_code, 0.0):
+ status = "Partially Ordered"
+
+ return status
+
+ def is_fully_ordered(self):
+ return self.get_ordered_status() == "Ordered"
+
+ def is_partially_ordered(self):
+ return self.get_ordered_status() == "Partially Ordered"
def update_lead(self):
if self.quotation_to == "Lead" and self.party_name:
@@ -103,7 +127,7 @@ class Quotation(SellingController):
@frappe.whitelist()
def declare_enquiry_lost(self, lost_reasons_list, competitors, detailed_reason=None):
- if not self.has_sales_order():
+ if not (self.is_fully_ordered() or self.is_partially_ordered()):
get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"])
lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons]
frappe.db.set(self, "status", "Lost")
@@ -243,7 +267,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
def set_expired_status():
# filter out submitted non expired quotations whose validity has been ended
- cond = "qo.docstatus = 1 and qo.status != 'Expired' and qo.valid_till < %s"
+ cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status != 'Expired' and `tabQuotation`.valid_till < %s"
# check if those QUO have SO against it
so_against_quo = """
SELECT
@@ -251,13 +275,18 @@ def set_expired_status():
WHERE
so_item.docstatus = 1 and so.docstatus = 1
and so_item.parent = so.name
- and so_item.prevdoc_docname = qo.name"""
+ and so_item.prevdoc_docname = `tabQuotation`.name"""
# if not exists any SO, set status as Expired
- frappe.db.sql(
- """UPDATE `tabQuotation` qo SET qo.status = 'Expired' WHERE {cond} and not exists({so_against_quo})""".format(
- cond=cond, so_against_quo=so_against_quo
- ),
+ frappe.db.multisql(
+ {
+ "mariadb": """UPDATE `tabQuotation` SET `tabQuotation`.status = 'Expired' WHERE {cond} and not exists({so_against_quo})""".format(
+ cond=cond, so_against_quo=so_against_quo
+ ),
+ "postgres": """UPDATE `tabQuotation` SET status = 'Expired' FROM `tabSales Order`, `tabSales Order Item` WHERE {cond} and not exists({so_against_quo})""".format(
+ cond=cond, so_against_quo=so_against_quo
+ ),
+ },
(nowdate()),
)
diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js
index 4c8f9c4f84..32fce1f2ad 100644
--- a/erpnext/selling/doctype/quotation/quotation_list.js
+++ b/erpnext/selling/doctype/quotation/quotation_list.js
@@ -25,6 +25,8 @@ frappe.listview_settings['Quotation'] = {
get_indicator: function(doc) {
if(doc.status==="Open") {
return [__("Open"), "orange", "status,=,Open"];
+ } else if (doc.status==="Partially Ordered") {
+ return [__("Partially Ordered"), "yellow", "status,=,Partially Ordered"];
} else if(doc.status==="Ordered") {
return [__("Ordered"), "green", "status,=,Ordered"];
} else if(doc.status==="Lost") {
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 7522e92a8a..8c03cb5b41 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -25,6 +25,7 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
from erpnext.selling.doctype.customer.customer import check_credit_limit
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.item.item import get_item_defaults
+from erpnext.stock.get_item_details import get_default_bom
from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
@@ -423,8 +424,9 @@ class SalesOrder(SellingController):
for table in [self.items, self.packed_items]:
for i in table:
- bom = get_default_bom_item(i.item_code)
+ bom = get_default_bom(i.item_code)
stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
+
if not for_raw_material_request:
total_work_order_qty = flt(
frappe.db.sql(
@@ -438,32 +440,19 @@ class SalesOrder(SellingController):
pending_qty = stock_qty
if pending_qty and i.item_code not in product_bundle_parents:
- if bom:
- items.append(
- dict(
- name=i.name,
- item_code=i.item_code,
- description=i.description,
- bom=bom,
- warehouse=i.warehouse,
- pending_qty=pending_qty,
- required_qty=pending_qty if for_raw_material_request else 0,
- sales_order_item=i.name,
- )
- )
- else:
- items.append(
- dict(
- name=i.name,
- item_code=i.item_code,
- description=i.description,
- bom="",
- warehouse=i.warehouse,
- pending_qty=pending_qty,
- required_qty=pending_qty if for_raw_material_request else 0,
- sales_order_item=i.name,
- )
+ items.append(
+ dict(
+ name=i.name,
+ item_code=i.item_code,
+ description=i.description,
+ bom=bom or "",
+ warehouse=i.warehouse,
+ pending_qty=pending_qty,
+ required_qty=pending_qty if for_raw_material_request else 0,
+ sales_order_item=i.name,
)
+ )
+
return items
def on_recurring(self, reference_doc, auto_repeat_doc):
@@ -1167,13 +1156,6 @@ def update_status(status, name):
so.update_status(status)
-def get_default_bom_item(item_code):
- bom = frappe.get_all("BOM", dict(item=item_code, is_active=True), order_by="is_default desc")
- bom = bom[0].name if bom else None
-
- return bom
-
-
@frappe.whitelist()
def make_raw_material_request(items, company, sales_order, project=None):
if not frappe.has_permission("Sales Order", "write"):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 96308f0bee..e5e317c506 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -329,7 +329,7 @@ class TestSalesOrder(FrappeTestCase):
def test_sales_order_on_hold(self):
so = make_sales_order(item_code="_Test Product Bundle Item")
- so.db_set("Status", "On Hold")
+ so.db_set("status", "On Hold")
si = make_sales_invoice(so.name)
self.assertRaises(frappe.ValidationError, create_dn_against_so, so.name)
self.assertRaises(frappe.ValidationError, si.submit)
@@ -644,7 +644,7 @@ class TestSalesOrder(FrappeTestCase):
else:
# update valid from
frappe.db.sql(
- """UPDATE `tabItem Tax` set valid_from = CURDATE()
+ """UPDATE `tabItem Tax` set valid_from = CURRENT_DATE
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template},
)
@@ -1380,6 +1380,59 @@ class TestSalesOrder(FrappeTestCase):
except Exception:
self.fail("Can not cancel sales order with linked cancelled payment entry")
+ def test_work_order_pop_up_from_sales_order(self):
+ "Test `get_work_order_items` in Sales Order picks the right BOM for items to manufacture."
+
+ from erpnext.controllers.item_variant import create_variant
+ from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+ make_item( # template item
+ "Test-WO-Tshirt",
+ {
+ "has_variant": 1,
+ "variant_based_on": "Item Attribute",
+ "attributes": [{"attribute": "Test Colour"}],
+ },
+ )
+ make_item("Test-RM-Cotton") # RM for BOM
+
+ for colour in (
+ "Red",
+ "Green",
+ ):
+ variant = create_variant("Test-WO-Tshirt", {"Test Colour": colour})
+ variant.save()
+
+ template_bom = make_bom(item="Test-WO-Tshirt", rate=100, raw_materials=["Test-RM-Cotton"])
+ red_var_bom = make_bom(item="Test-WO-Tshirt-R", rate=100, raw_materials=["Test-RM-Cotton"])
+
+ so = make_sales_order(
+ **{
+ "item_list": [
+ {
+ "item_code": "Test-WO-Tshirt-R",
+ "qty": 1,
+ "rate": 1000,
+ "warehouse": "_Test Warehouse - _TC",
+ },
+ {
+ "item_code": "Test-WO-Tshirt-G",
+ "qty": 1,
+ "rate": 1000,
+ "warehouse": "_Test Warehouse - _TC",
+ },
+ ]
+ }
+ )
+ wo_items = so.get_work_order_items()
+
+ self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
+ self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
+
+ # Must pick Template Item BOM for Test-WO-Tshirt-G as it has no BOM
+ self.assertEqual(wo_items[1].get("item_code"), "Test-WO-Tshirt-G")
+ self.assertEqual(wo_items[1].get("bom"), template_bom.name)
+
def test_request_for_raw_materials(self):
item = make_item(
"_Test Finished Item",
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 3797856db2..318799907e 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -23,7 +23,6 @@
"quantity_and_rate",
"qty",
"stock_uom",
- "picked_qty",
"col_break2",
"uom",
"conversion_factor",
@@ -87,6 +86,7 @@
"delivered_qty",
"produced_qty",
"returned_qty",
+ "picked_qty",
"shopping_cart_section",
"additional_notes",
"section_break_63",
@@ -198,6 +198,7 @@
"width": "100px"
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
@@ -220,6 +221,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "UOM Conversion Factor",
@@ -228,6 +230,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "stock_qty",
"fieldtype": "Float",
"label": "Qty as per Stock UOM",
@@ -811,7 +814,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-04-27 03:15:34.366563",
+ "modified": "2022-06-17 05:27:41.603006",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 99afe813cb..13d5069ea6 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -107,7 +107,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te
ORDER BY
item.name asc
LIMIT
- {start}, {page_length}""".format(
+ {page_length} offset {start}""".format(
start=start,
page_length=page_length,
lft=lft,
@@ -204,7 +204,7 @@ def item_group_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
""" select distinct name from `tabItem Group`
- where {condition} and (name like %(txt)s) limit {start}, {page_len}""".format(
+ where {condition} and (name like %(txt)s) limit {page_len} offset {start}""".format(
condition=cond, start=start, page_len=page_len
),
{"txt": "%%%s%%" % txt},
diff --git a/erpnext/selling/report/inactive_customers/inactive_customers.py b/erpnext/selling/report/inactive_customers/inactive_customers.py
index 1b337fc495..a166085327 100644
--- a/erpnext/selling/report/inactive_customers/inactive_customers.py
+++ b/erpnext/selling/report/inactive_customers/inactive_customers.py
@@ -31,13 +31,13 @@ def execute(filters=None):
def get_sales_details(doctype):
cond = """sum(so.base_net_total) as 'total_order_considered',
max(so.posting_date) as 'last_order_date',
- DATEDIFF(CURDATE(), max(so.posting_date)) as 'days_since_last_order' """
+ DATEDIFF(CURRENT_DATE, max(so.posting_date)) as 'days_since_last_order' """
if doctype == "Sales Order":
cond = """sum(if(so.status = "Stopped",
so.base_net_total * so.per_delivered/100,
so.base_net_total)) as 'total_order_considered',
max(so.transaction_date) as 'last_order_date',
- DATEDIFF(CURDATE(), max(so.transaction_date)) as 'days_since_last_order'"""
+ DATEDIFF(CURRENT_DATE, max(so.transaction_date)) as 'days_since_last_order'"""
return frappe.db.sql(
"""select
diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
index cc1055c787..928ed80d5c 100644
--- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
+++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
@@ -65,7 +65,7 @@ def get_data():
WHERE
so.docstatus = 1
and so.name = so_item.parent
- and so.status not in ("Closed","Completed","Cancelled")
+ and so.status not in ('Closed','Completed','Cancelled')
GROUP BY
so.name,so_item.item_code
""",
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index cc61594af4..720aa41982 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -64,7 +64,7 @@ def get_data(conditions, filters):
soi.delivery_date as delivery_date,
so.name as sales_order,
so.status, so.customer, soi.item_code,
- DATEDIFF(CURDATE(), soi.delivery_date) as delay_days,
+ DATEDIFF(CURRENT_DATE, soi.delivery_date) as delay_days,
IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
soi.qty, soi.delivered_qty,
(soi.qty - soi.delivered_qty) AS pending_qty,
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 9bde6e2c47..9ffd6dfddc 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -464,7 +464,7 @@ class Company(NestedSet):
# reset default company
frappe.db.sql(
- """update `tabSingles` set value=""
+ """update `tabSingles` set value=''
where doctype='Global Defaults' and field='default_company'
and value=%s""",
self.name,
@@ -472,7 +472,7 @@ class Company(NestedSet):
# reset default company
frappe.db.sql(
- """update `tabSingles` set value=""
+ """update `tabSingles` set value=''
where doctype='Chart of Accounts Importer' and field='company'
and value=%s""",
self.name,
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index cdfea7764f..4fc20e6103 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -198,7 +198,7 @@ class EmailDigest(Document):
todo_list = frappe.db.sql(
"""select *
- from `tabToDo` where (owner=%s or assigned_by=%s) and status="Open"
+ from `tabToDo` where (owner=%s or assigned_by=%s) and status='Open'
order by field(priority, 'High', 'Medium', 'Low') asc, date asc limit 20""",
(user_id, user_id),
as_dict=True,
@@ -854,7 +854,7 @@ class EmailDigest(Document):
sql_po = """select {fields} from `tabPurchase Order Item`
left join `tabPurchase Order` on `tabPurchase Order`.name = `tabPurchase Order Item`.parent
- where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and curdate() > `tabPurchase Order Item`.schedule_date
+ where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and CURRENT_DATE > `tabPurchase Order Item`.schedule_date
and received_qty < qty order by `tabPurchase Order Item`.parent DESC,
`tabPurchase Order Item`.schedule_date DESC""".format(
fields=fields_po
@@ -862,7 +862,7 @@ class EmailDigest(Document):
sql_poi = """select {fields} from `tabPurchase Order Item`
left join `tabPurchase Order` on `tabPurchase Order`.name = `tabPurchase Order Item`.parent
- where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and curdate() > `tabPurchase Order Item`.schedule_date
+ where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and CURRENT_DATE > `tabPurchase Order Item`.schedule_date
and received_qty < qty order by `tabPurchase Order Item`.idx""".format(
fields=fields_poi
)
diff --git a/erpnext/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py
index d07ab08450..cf7cba8452 100644
--- a/erpnext/setup/doctype/party_type/party_type.py
+++ b/erpnext/setup/doctype/party_type/party_type.py
@@ -21,7 +21,7 @@ def get_party_type(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
"""select name from `tabParty Type`
where `{key}` LIKE %(txt)s {cond}
- order by name limit %(start)s, %(page_len)s""".format(
+ order by name limit %(page_len)s offset %(start)s""".format(
key=searchfield, cond=cond
),
{"txt": "%" + txt + "%", "start": start, "page_len": page_len},
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
index 78b3939012..7c478bb4ed 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -42,7 +42,7 @@ class TransactionDeletionRecord(Document):
def delete_bins(self):
frappe.db.sql(
- """delete from tabBin where warehouse in
+ """delete from `tabBin` where warehouse in
(select name from tabWarehouse where company=%s)""",
self.company,
)
@@ -64,7 +64,7 @@ class TransactionDeletionRecord(Document):
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
frappe.db.sql(
- """delete from tabAddress where name in ({addresses}) and
+ """delete from `tabAddress` where name in ({addresses}) and
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
and dl1.link_doctype<>dl2.link_doctype)""".format(
@@ -80,7 +80,7 @@ class TransactionDeletionRecord(Document):
)
frappe.db.sql(
- """update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(
+ """update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format(
leads=",".join(leads)
)
)
@@ -178,7 +178,7 @@ class TransactionDeletionRecord(Document):
else:
last = 0
- frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix))
+ frappe.db.sql("""update `tabSeries` set current = %s where name=%s""", (last, prefix))
def delete_version_log(self, doctype, company_fieldname):
frappe.db.sql(
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 559883f224..52854a0f01 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -335,7 +335,7 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
and `tabStock Ledger Entry`.is_cancelled = 0
- and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
+ and (`tabBatch`.expiry_date >= CURRENT_DATE or `tabBatch`.expiry_date IS NULL) {0}
group by batch_id
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
""".format(
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 2d7abc8a0d..2de4842ebe 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -184,6 +184,7 @@
"width": "100px"
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
@@ -209,6 +210,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "UOM Conversion Factor",
@@ -217,6 +219,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "stock_qty",
"fieldtype": "Float",
"label": "Qty in Stock UOM",
@@ -780,7 +783,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-05-02 12:09:39.610075",
+ "modified": "2022-06-17 05:25:47.711177",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 73b250db54..ff95c500ab 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -263,9 +263,9 @@ def get_default_contact(out, name):
FROM
`tabDynamic Link` dl
WHERE
- dl.link_doctype="Customer"
+ dl.link_doctype='Customer'
AND dl.link_name=%s
- AND dl.parenttype = "Contact"
+ AND dl.parenttype = 'Contact'
""",
(name),
as_dict=1,
@@ -289,9 +289,9 @@ def get_default_address(out, name):
FROM
`tabDynamic Link` dl
WHERE
- dl.link_doctype="Customer"
+ dl.link_doctype='Customer'
AND dl.link_name=%s
- AND dl.parenttype = "Address"
+ AND dl.parenttype = 'Address'
""",
(name),
as_dict=1,
@@ -388,7 +388,7 @@ def notify_customers(delivery_trip):
if email_recipients:
frappe.msgprint(_("Email sent to {0}").format(", ".join(email_recipients)))
- delivery_trip.db_set("email_notification_sent", True)
+ delivery_trip.db_set("email_notification_sent", 1)
else:
frappe.msgprint(_("No contacts with email IDs found."))
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 2f6d4fb783..76cb31dc42 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -14,7 +14,6 @@
"details",
"naming_series",
"item_code",
- "variant_of",
"item_name",
"item_group",
"stock_uom",
@@ -22,6 +21,7 @@
"disabled",
"allow_alternative_item",
"is_stock_item",
+ "has_variants",
"include_item_in_manufacturing",
"opening_stock",
"valuation_rate",
@@ -66,7 +66,7 @@
"has_serial_no",
"serial_no_series",
"variants_section",
- "has_variants",
+ "variant_of",
"variant_based_on",
"attributes",
"accounting",
@@ -112,8 +112,8 @@
"quality_inspection_template",
"inspection_required_before_delivery",
"manufacturing",
- "default_bom",
"is_sub_contracted_item",
+ "default_bom",
"column_break_74",
"customer_code",
"default_item_manufacturer",
@@ -479,7 +479,7 @@
"collapsible_depends_on": "attributes",
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "variants_section",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Variants"
},
{
@@ -504,7 +504,8 @@
"fieldname": "attributes",
"fieldtype": "Table",
"hidden": 1,
- "label": "Attributes",
+ "label": "Variant Attributes",
+ "mandatory_depends_on": "has_variants",
"options": "Item Variant Attribute"
},
{
@@ -909,7 +910,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-06-08 11:35:20.094546",
+ "modified": "2022-06-15 09:02:06.177691",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index b2f5fb7d20..87fa72d74f 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -1155,7 +1155,7 @@ def check_stock_uom_with_bin(item, stock_uom):
bin_list = frappe.db.sql(
"""
- select * from tabBin where item_code = %s
+ select * from `tabBin` where item_code = %s
and (reserved_qty > 0 or ordered_qty > 0 or indented_qty > 0 or planned_qty > 0)
and stock_uom != %s
""",
@@ -1171,7 +1171,7 @@ def check_stock_uom_with_bin(item, stock_uom):
)
# No SLE or documents against item. Bin UOM can be changed safely.
- frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
+ frappe.db.sql("""update `tabBin` set stock_uom=%s where item_code=%s""", (stock_uom, item))
def get_item_defaults(item_code, company):
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index aa0a5490b6..3366c737cb 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -381,8 +381,8 @@ class TestItem(FrappeTestCase):
frappe.delete_doc_if_exists("Item Attribute", "Test Item Length")
frappe.db.sql(
- '''delete from `tabItem Variant Attribute`
- where attribute="Test Item Length"'''
+ """delete from `tabItem Variant Attribute`
+ where attribute='Test Item Length' """
)
frappe.flags.attribute_values = None
@@ -800,6 +800,7 @@ def create_item(
item_code,
is_stock_item=1,
valuation_rate=0,
+ stock_uom="Nos",
warehouse="_Test Warehouse - _TC",
is_customer_provided_item=None,
customer=None,
@@ -815,6 +816,7 @@ def create_item(
item.item_name = item_code
item.description = item_code
item.item_group = "All Item Groups"
+ item.stock_uom = stock_uom
item.is_stock_item = is_stock_item
item.is_fixed_asset = is_fixed_asset
item.asset_category = asset_category
diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py
index 0f93bb9e95..fb1a28d846 100644
--- a/erpnext/stock/doctype/item_alternative/item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/item_alternative.py
@@ -77,7 +77,7 @@ def get_alternative_items(doctype, txt, searchfield, start, page_len, filters):
union
(select item_code from `tabItem Alternative`
where alternative_item_code = %(item_code)s and item_code like %(txt)s
- and two_way = 1) limit {0}, {1}
+ and two_way = 1) limit {1} offset {0}
""".format(
start, page_len
),
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 1af9953451..1ba801134e 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -24,7 +24,7 @@ class TestLandedCostVoucher(FrappeTestCase):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
get_multiple_items=True,
get_taxes_and_charges=True,
)
@@ -195,7 +195,7 @@ class TestLandedCostVoucher(FrappeTestCase):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
get_multiple_items=True,
get_taxes_and_charges=True,
do_not_submit=True,
@@ -280,7 +280,7 @@ class TestLandedCostVoucher(FrappeTestCase):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
do_not_save=True,
)
pr.items[0].cost_center = "Main - TCP1"
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index e9ccf5fc77..e5b9de8789 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -203,7 +203,7 @@ def item_details(doctype, txt, searchfield, start, page_len, filters):
where name in ( select item_code FROM `tabDelivery Note Item`
where parent= %s)
and %s like "%s" %s
- limit %s, %s """
+ limit %s offset %s """
% ("%s", searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
- ((filters or {}).get("delivery_note"), "%%%s%%" % txt, start, page_len),
+ ((filters or {}).get("delivery_note"), "%%%s%%" % txt, page_len, start),
)
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 7dc3ba049c..d31d695c80 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -699,7 +699,7 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
AND `company` = %(company)s
AND `name` like %(txt)s
ORDER BY
- if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
+ (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end) name
LIMIT
%(start)s, %(page_length)s""",
{
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 7fbfa62939..be4f27465e 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -276,7 +276,7 @@ class TestPurchaseReceipt(FrappeTestCase):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
get_multiple_items=True,
get_taxes_and_charges=True,
)
@@ -486,13 +486,13 @@ class TestPurchaseReceipt(FrappeTestCase):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
)
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-2,
@@ -573,13 +573,13 @@ class TestPurchaseReceipt(FrappeTestCase):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
)
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-5,
@@ -615,7 +615,7 @@ class TestPurchaseReceipt(FrappeTestCase):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
qty=2,
rejected_qty=2,
rejected_warehouse=rejected_warehouse,
@@ -624,7 +624,7 @@ class TestPurchaseReceipt(FrappeTestCase):
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-2,
@@ -951,7 +951,7 @@ class TestPurchaseReceipt(FrappeTestCase):
cost_center=cost_center,
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
)
stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
@@ -975,7 +975,7 @@ class TestPurchaseReceipt(FrappeTestCase):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work in Progress - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
)
stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 1c65ac86c9..b45d66391c 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -252,6 +252,7 @@
"width": "100px"
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
@@ -265,6 +266,7 @@
"width": "100px"
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "Conversion Factor",
@@ -547,6 +549,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "stock_qty",
"fieldtype": "Float",
"label": "Accepted Qty in Stock UOM",
@@ -878,7 +881,7 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "returned_qty",
+ "depends_on": "doc.returned_qty",
"fieldname": "returned_qty",
"fieldtype": "Float",
"label": "Returned Qty in Stock UOM",
@@ -887,6 +890,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "received_stock_qty",
"fieldtype": "Float",
"label": "Received Qty in Stock UOM",
@@ -994,7 +998,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-04-11 13:07:32.061402",
+ "modified": "2022-06-17 05:32:16.483178",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 331d3e812b..13abfad455 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -232,7 +232,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
FROM `tab{doc}`
WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
{qi_condition} {cond} {mcond}
- ORDER BY item_code limit {start}, {page_len}
+ ORDER BY item_code limit {page_len} offset {start}
""".format(
doc=filters.get("from"),
cond=cond,
@@ -252,7 +252,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s
{qi_condition} {cond} {mcond}
ORDER BY production_item
- LIMIT {start}, {page_len}
+ limit {page_len} offset {start}
""".format(
doc=filters.get("from"),
cond=cond,
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
index 156f77f5ac..e093933829 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -25,7 +25,8 @@
"items_to_be_repost",
"affected_transactions",
"distinct_item_and_warehouse",
- "current_index"
+ "current_index",
+ "gl_reposting_index"
],
"fields": [
{
@@ -181,12 +182,20 @@
"label": "Affected Transactions",
"no_copy": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "gl_reposting_index",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "GL reposting index",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-04-18 14:08:08.821602",
+ "modified": "2022-06-13 12:20:22.182322",
"modified_by": "Administrator",
"module": "Stock",
"name": "Repost Item Valuation",
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 328afc86be..b1017d2c9c 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -87,6 +87,7 @@ class RepostItemValuation(Document):
self.current_index = 0
self.distinct_item_and_warehouse = None
self.items_to_be_repost = None
+ self.gl_reposting_index = 0
self.db_update()
def deduplicate_similar_repost(self):
@@ -192,6 +193,7 @@ def repost_gl_entries(doc):
directly_dependent_transactions + list(repost_affected_transaction),
doc.posting_date,
doc.company,
+ repost_doc=doc,
)
diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
index 3184f69aa4..edd2553d5d 100644
--- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
@@ -2,10 +2,14 @@
# See license.txt
+from unittest.mock import MagicMock, call
+
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
+from frappe.utils.data import add_to_date, today
+from erpnext.accounts.utils import repost_gle_for_stock_vouchers
from erpnext.controllers.stock_controller import create_item_wise_repost_entries
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -13,10 +17,11 @@ from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import (
in_configured_timeslot,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.tests.test_utils import StockTestMixin
from erpnext.stock.utils import PendingRepostingError
-class TestRepostItemValuation(FrappeTestCase):
+class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
def tearDown(self):
frappe.flags.dont_execute_stock_reposts = False
@@ -193,3 +198,77 @@ class TestRepostItemValuation(FrappeTestCase):
[["a", "b"], ["c", "d"]],
sorted(frappe.parse_json(frappe.as_json(set([("a", "b"), ("c", "d")])))),
)
+
+ def test_gl_repost_progress(self):
+ from erpnext.accounts import utils
+
+ # lower numbers to simplify test
+ orig_chunk_size = utils.GL_REPOSTING_CHUNK
+ utils.GL_REPOSTING_CHUNK = 1
+ self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size)
+
+ doc = frappe.new_doc("Repost Item Valuation")
+ doc.db_set = MagicMock()
+
+ vouchers = []
+ company = "_Test Company with perpetual inventory"
+ posting_date = today()
+
+ for _ in range(3):
+ se = make_stock_entry(company=company, qty=1, rate=2, target="Stores - TCP1")
+ vouchers.append((se.doctype, se.name))
+
+ repost_gle_for_stock_vouchers(stock_vouchers=vouchers, posting_date=posting_date, repost_doc=doc)
+ self.assertIn(call("gl_reposting_index", 1), doc.db_set.mock_calls)
+ doc.db_set.reset_mock()
+
+ doc.gl_reposting_index = 1
+ repost_gle_for_stock_vouchers(stock_vouchers=vouchers, posting_date=posting_date, repost_doc=doc)
+
+ self.assertNotIn(call("gl_reposting_index", 1), doc.db_set.mock_calls)
+
+ def test_gl_complete_gl_reposting(self):
+ from erpnext.accounts import utils
+
+ # lower numbers to simplify test
+ orig_chunk_size = utils.GL_REPOSTING_CHUNK
+ utils.GL_REPOSTING_CHUNK = 2
+ self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size)
+
+ item = self.make_item().name
+
+ company = "_Test Company with perpetual inventory"
+
+ for _ in range(10):
+ make_stock_entry(item=item, company=company, qty=1, rate=10, target="Stores - TCP1")
+
+ # consume
+ consumption = make_stock_entry(item=item, company=company, qty=1, source="Stores - TCP1")
+
+ self.assertGLEs(
+ consumption,
+ [{"credit": 10, "debit": 0}],
+ gle_filters={"account": "Stock In Hand - TCP1"},
+ )
+
+ # backdated receipt
+ backdated_receipt = make_stock_entry(
+ item=item,
+ company=company,
+ qty=1,
+ rate=50,
+ target="Stores - TCP1",
+ posting_date=add_to_date(today(), days=-1),
+ )
+ self.assertGLEs(
+ backdated_receipt,
+ [{"credit": 0, "debit": 50}],
+ gle_filters={"account": "Stock In Hand - TCP1"},
+ )
+
+ # check that original consumption GLe is updated
+ self.assertGLEs(
+ consumption,
+ [{"credit": 50, "debit": 0}],
+ gle_filters={"account": "Stock In Hand - TCP1"},
+ )
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f1df54dd6a..e902d1e56b 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -590,7 +590,7 @@ class StockEntry(StockController):
)
+ "
"
+ _("Available quantity is {0}, you need {1}").format(
- frappe.bold(d.actual_qty), frappe.bold(d.transfer_qty)
+ frappe.bold(flt(d.actual_qty, d.precision("actual_qty"))), frappe.bold(d.transfer_qty)
),
NegativeStockError,
title=_("Insufficient Stock"),
@@ -1980,23 +1980,30 @@ class StockEntry(StockController):
):
# Get PO Supplied Items Details
- item_wh = frappe._dict(
- frappe.db.sql(
- """
- select rm_item_code, reserve_warehouse
- from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
- where po.name = poitemsup.parent
- and po.name = %s""",
- self.purchase_order,
- )
+ po_supplied_items = frappe.db.get_all(
+ "Purchase Order Item Supplied",
+ filters={"parent": self.purchase_order},
+ fields=["name", "rm_item_code", "reserve_warehouse"],
)
+ # Get Items Supplied in Stock Entries against PO
supplied_items = get_supplied_items(self.purchase_order)
- for name, item in supplied_items.items():
- frappe.db.set_value("Purchase Order Item Supplied", name, item)
- # Update reserved sub contracted quantity in bin based on Supplied Item Details and
+ for row in po_supplied_items:
+ key, item = row.name, {}
+ if not supplied_items.get(key):
+ # no stock transferred against PO Supplied Items row
+ item = {"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0}
+ else:
+ item = supplied_items.get(key)
+
+ frappe.db.set_value("Purchase Order Item Supplied", row.name, item)
+
+ # RM Item-Reserve Warehouse Dict
+ item_wh = {x.get("rm_item_code"): x.get("reserve_warehouse") for x in po_supplied_items}
+
for d in self.get("items"):
+ # Update reserved sub contracted quantity in bin based on Supplied Item Details and
item_code = d.get("original_item") or d.get("item_code")
reserve_warehouse = item_wh.get(item_code)
if not (reserve_warehouse and item_code):
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index 83aed904dd..d758c8a0ea 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -233,6 +233,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "Conversion Factor",
@@ -242,6 +243,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
@@ -253,6 +255,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "transfer_qty",
"fieldtype": "Float",
"label": "Qty as per Stock UOM",
@@ -556,7 +559,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-02-26 00:51:24.963653",
+ "modified": "2022-06-17 05:06:33.621264",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index 55a213ccc3..f669e90308 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -42,6 +42,9 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
"delete from `tabBin` where item_code in (%s)" % (", ".join(["%s"] * len(items))), items
)
+ def tearDown(self):
+ frappe.db.rollback()
+
def test_item_cost_reposting(self):
company = "_Test Company"
@@ -1230,6 +1233,93 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
)
self.assertEqual(abs(sles[0].stock_value_difference), sles[1].stock_value_difference)
+ @change_settings("System Settings", {"float_precision": 4})
+ def test_negative_qty_with_precision(self):
+ "Test if system precision is respected while validating negative qty."
+ from erpnext.stock.doctype.item.test_item import create_item
+ from erpnext.stock.utils import get_stock_balance
+
+ item_code = "ItemPrecisionTest"
+ warehouse = "_Test Warehouse - _TC"
+ create_item(item_code, is_stock_item=1, stock_uom="Kg")
+
+ create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=559.8327, rate=100)
+
+ make_stock_entry(item_code=item_code, source=warehouse, qty=470.84, rate=100)
+ self.assertEqual(get_stock_balance(item_code, warehouse), 88.9927)
+
+ settings = frappe.get_doc("System Settings")
+ settings.float_precision = 3
+ settings.save()
+
+ # To deliver 100 qty we fall short of 11.0073 qty (11.007 with precision 3)
+ # Stock up with 11.007 (balance in db becomes 99.9997, on UI it will show as 100)
+ make_stock_entry(item_code=item_code, target=warehouse, qty=11.007, rate=100)
+ self.assertEqual(get_stock_balance(item_code, warehouse), 99.9997)
+
+ # See if delivery note goes through
+ # Negative qty error should not be raised as 99.9997 is 100 with precision 3 (system precision)
+ dn = create_delivery_note(
+ item_code=item_code,
+ qty=100,
+ rate=150,
+ warehouse=warehouse,
+ company="_Test Company",
+ expense_account="Cost of Goods Sold - _TC",
+ cost_center="Main - _TC",
+ do_not_submit=True,
+ )
+ dn.submit()
+
+ self.assertEqual(flt(get_stock_balance(item_code, warehouse), 3), 0.000)
+
+ @change_settings("System Settings", {"float_precision": 4})
+ def test_future_negative_qty_with_precision(self):
+ """
+ Ledger:
+ | Voucher | Qty | Balance
+ -------------------
+ | Reco | 559.8327| 559.8327
+ | SE | -470.84 | [Backdated] (new bal: 88.9927)
+ | SE | 11.007 | 570.8397 (new bal: 99.9997)
+ | DN | -100 | 470.8397 (new bal: -0.0003)
+
+ Check if future negative qty is asserted as per precision 3.
+ -0.0003 should be considered as 0.000
+ """
+ from erpnext.stock.doctype.item.test_item import create_item
+
+ item_code = "ItemPrecisionTest"
+ warehouse = "_Test Warehouse - _TC"
+ create_item(item_code, is_stock_item=1, stock_uom="Kg")
+
+ create_stock_reconciliation(
+ item_code=item_code,
+ warehouse=warehouse,
+ qty=559.8327,
+ rate=100,
+ posting_date=add_days(today(), -2),
+ )
+ make_stock_entry(item_code=item_code, target=warehouse, qty=11.007, rate=100)
+ create_delivery_note(
+ item_code=item_code,
+ qty=100,
+ rate=150,
+ warehouse=warehouse,
+ company="_Test Company",
+ expense_account="Cost of Goods Sold - _TC",
+ cost_center="Main - _TC",
+ )
+
+ settings = frappe.get_doc("System Settings")
+ settings.float_precision = 3
+ settings.save()
+
+ # Make backdated SE and make sure SE goes through as per precision (no negative qty error)
+ make_stock_entry(
+ item_code=item_code, source=warehouse, qty=470.84, rate=100, posting_date=add_days(today(), -1)
+ )
+
def create_repack_entry(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index bd60cf0a5a..23e0f1efaf 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -611,7 +611,7 @@ def get_items_for_stock_reco(warehouse, company):
select
i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no
from
- tabBin bin, tabItem i
+ `tabBin` bin, `tabItem` i
where
i.name = bin.item_code
and IFNULL(i.disabled, 0) = 0
@@ -629,7 +629,7 @@ def get_items_for_stock_reco(warehouse, company):
select
i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no
from
- tabItem i, `tabItem Default` id
+ `tabItem` i, `tabItem Default` id
where
i.name = id.parent
and exists(
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index df16643d46..ab784ca107 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -161,8 +161,7 @@ def get_children(doctype, parent=None, company=None, is_root=False):
fields = ["name as value", "is_group as expandable"]
filters = [
- ["docstatus", "<", "2"],
- ['ifnull(`parent_warehouse`, "")', "=", parent],
+ ["ifnull(`parent_warehouse`, '')", "=", parent],
["company", "in", (company, None, "")],
]
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index c8d9f5404f..38ad662b6a 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -63,18 +63,16 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
item = frappe.get_cached_doc("Item", args.item_code)
validate_item_details(args, item)
- out = get_basic_details(args, item, overwrite_warehouse)
-
if isinstance(doc, str):
doc = json.loads(doc)
- if doc and doc.get("doctype") == "Purchase Invoice":
- args["bill_date"] = doc.get("bill_date")
-
if doc:
- args["posting_date"] = doc.get("posting_date")
- args["transaction_date"] = doc.get("transaction_date")
+ args["transaction_date"] = doc.get("transaction_date") or doc.get("posting_date")
+ if doc.get("doctype") == "Purchase Invoice":
+ args["bill_date"] = doc.get("bill_date")
+
+ out = get_basic_details(args, item, overwrite_warehouse)
get_item_tax_template(args, item, out)
out["item_tax_rate"] = get_item_tax_map(
args.company,
@@ -596,9 +594,7 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
if tax.valid_from or tax.maximum_net_rate:
# In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date
- validation_date = (
- args.get("transaction_date") or args.get("bill_date") or args.get("posting_date")
- )
+ validation_date = args.get("bill_date") or args.get("transaction_date")
if getdate(tax.valid_from) <= getdate(validation_date) and is_within_valid_range(args, tax):
taxes_with_validity.append(tax)
@@ -891,14 +887,10 @@ def get_item_price(args, item_code, ignore_party=False):
conditions += """ and %(transaction_date)s between
ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
- if args.get("posting_date"):
- conditions += """ and %(posting_date)s between
- ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
-
return frappe.db.sql(
""" select name, price_list_rate, uom
from `tabItem Price` {conditions}
- order by valid_from desc, batch_no desc, uom desc """.format(
+ order by valid_from desc, ifnull(batch_no, '') desc, uom desc """.format(
conditions=conditions
),
args,
@@ -921,7 +913,6 @@ def get_price_list_rate_for(args, item_code):
"supplier": args.get("supplier"),
"uom": args.get("uom"),
"transaction_date": args.get("transaction_date"),
- "posting_date": args.get("posting_date"),
"batch_no": args.get("batch_no"),
}
@@ -1352,12 +1343,22 @@ def get_price_list_currency_and_exchange_rate(args):
@frappe.whitelist()
def get_default_bom(item_code=None):
- if item_code:
- bom = frappe.db.get_value(
- "BOM", {"docstatus": 1, "is_default": 1, "is_active": 1, "item": item_code}
+ def _get_bom(item):
+ bom = frappe.get_all(
+ "BOM", dict(item=item, is_active=True, is_default=True, docstatus=1), limit=1
)
- if bom:
- return bom
+ return bom[0].name if bom else None
+
+ if not item_code:
+ return
+
+ bom_name = _get_bom(item_code)
+
+ template_item = frappe.db.get_value("Item", item_code, "variant_of")
+ if not bom_name and template_item:
+ bom_name = _get_bom(template_item)
+
+ return bom_name
@frappe.whitelist()
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index f19c75f54e..136c78ff58 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -105,7 +105,7 @@ def get_item_warehouse_projected_qty(items_to_consider):
for item_code, warehouse, projected_qty in frappe.db.sql(
"""select item_code, warehouse, projected_qty
from tabBin where item_code in ({0})
- and (warehouse != "" and warehouse is not null)""".format(
+ and (warehouse != '' and warehouse is not null)""".format(
", ".join(["%s"] * len(items_to_consider))
),
items_to_consider,
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
index bcc213905d..b68db356ea 100644
--- a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
@@ -73,7 +73,7 @@ def get_stock_ledger_entries(report_filters):
"Stock Ledger Entry",
fields=fields,
filters=filters,
- order_by="timestamp(posting_date, posting_time) asc, creation asc",
+ order_by="posting_date asc, posting_time asc, creation asc",
)
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
index 78c6961623..39d84a7d5b 100644
--- a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
@@ -106,7 +106,7 @@ def get_stock_ledger_entries(report_filters):
"Stock Ledger Entry",
fields=fields,
filters=filters,
- order_by="timestamp(posting_date, posting_time) asc, creation asc",
+ order_by="posting_date asc, posting_time asc, creation asc",
)
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 409e238657..ef1642e1f9 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -238,7 +238,7 @@ def get_stock_ledger_entries(filters, items):
sl_entries = frappe.db.sql(
"""
SELECT
- concat_ws(" ", posting_date, posting_time) AS date,
+ concat_ws(' ', posting_date, posting_time) AS date,
item_code,
warehouse,
actual_qty,
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index fbb5bf8e16..682ac5d507 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -174,7 +174,6 @@ def get_reserved_qty(item_code, warehouse):
)
)
.where(dnpi_parent.so_item_qty >= dnpi_parent.so_item_delivered_qty)
- )
reserved_qty = q.run()
return flt(reserved_qty[0][0]) if reserved_qty else 0
@@ -232,7 +231,7 @@ def get_planned_qty(item_code, warehouse):
planned_qty = frappe.db.sql(
"""
select sum(qty - produced_qty) from `tabWork Order`
- where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed", "Closed")
+ where production_item = %s and fg_warehouse = %s and status not in ('Stopped', 'Completed', 'Closed')
and docstatus=1 and qty > produced_qty""",
(item_code, warehouse),
)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 4789b52d50..ba2d3c1512 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import copy
@@ -370,7 +370,7 @@ class update_entries_after(object):
self.args["name"] = self.args.sle_id
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
- self.get_precision()
+ self.set_precision()
self.valuation_method = get_valuation_method(self.item_code)
self.new_items_found = False
@@ -381,10 +381,10 @@ class update_entries_after(object):
self.initialize_previous_data(self.args)
self.build()
- def get_precision(self):
- company_base_currency = frappe.get_cached_value("Company", self.company, "default_currency")
- self.precision = get_field_precision(
- frappe.get_meta("Stock Ledger Entry").get_field("stock_value"), currency=company_base_currency
+ def set_precision(self):
+ self.flt_precision = cint(frappe.db.get_default("float_precision")) or 2
+ self.currency_precision = get_field_precision(
+ frappe.get_meta("Stock Ledger Entry").get_field("stock_value")
)
def initialize_previous_data(self, args):
@@ -581,7 +581,7 @@ class update_entries_after(object):
self.update_queue_values(sle)
# rounding as per precision
- self.wh_data.stock_value = flt(self.wh_data.stock_value, self.precision)
+ self.wh_data.stock_value = flt(self.wh_data.stock_value, self.currency_precision)
if not self.wh_data.qty_after_transaction:
self.wh_data.stock_value = 0.0
stock_value_difference = self.wh_data.stock_value - self.wh_data.prev_stock_value
@@ -605,6 +605,7 @@ class update_entries_after(object):
will not consider cancelled entries
"""
diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty)
+ diff = flt(diff, self.flt_precision) # respect system precision
if diff < 0 and abs(diff) > 0.0001:
# negative stock!
@@ -1405,7 +1406,8 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
return
neg_sle = get_future_sle_with_negative_qty(args)
- if neg_sle:
+
+ if is_negative_with_precision(neg_sle):
message = _(
"{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
).format(
@@ -1423,7 +1425,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
return
neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
- if neg_batch_sle:
+ if is_negative_with_precision(neg_batch_sle, is_batch=True):
message = _(
"{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
).format(
@@ -1437,6 +1439,22 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
+def is_negative_with_precision(neg_sle, is_batch=False):
+ """
+ Returns whether system precision rounded qty is insufficient.
+ E.g: -0.0003 in precision 3 (0.000) is sufficient for the user.
+ """
+
+ if not neg_sle:
+ return False
+
+ field = "cumulative_total" if is_batch else "qty_after_transaction"
+ precision = cint(frappe.db.get_default("float_precision")) or 2
+ qty_deficit = flt(neg_sle[0][field], precision)
+
+ return qty_deficit < 0 and abs(qty_deficit) > 0.0001
+
+
def get_future_sle_with_negative_qty(args):
return frappe.db.sql(
"""
diff --git a/erpnext/stock/tests/test_utils.py b/erpnext/stock/tests/test_utils.py
index b046dbda24..4e93ac93cb 100644
--- a/erpnext/stock/tests/test_utils.py
+++ b/erpnext/stock/tests/test_utils.py
@@ -26,6 +26,7 @@ class StockTestMixin:
filters=filters,
order_by="timestamp(posting_date, posting_time), creation",
)
+ self.assertGreaterEqual(len(sles), len(expected_sles))
for exp_sle, act_sle in zip(expected_sles, sles):
for k, v in exp_sle.items():
@@ -49,7 +50,7 @@ class StockTestMixin:
filters=filters,
order_by=order_by or "posting_date, creation",
)
-
+ self.assertGreaterEqual(len(actual_gles), len(expected_gles))
for exp_gle, act_gle in zip(expected_gles, actual_gles):
for k, exp_value in exp_gle.items():
act_value = act_gle[k]
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 6d8fdaa404..9fb3be5188 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -499,7 +499,7 @@ def add_additional_uom_columns(columns, result, include_uom, conversion_factors)
def get_incoming_outgoing_rate_for_cancel(item_code, voucher_type, voucher_no, voucher_detail_no):
outgoing_rate = frappe.db.sql(
- """SELECT abs(stock_value_difference / actual_qty)
+ """SELECT CASE WHEN actual_qty = 0 THEN 0 ELSE abs(stock_value_difference / actual_qty) END
FROM `tabStock Ledger Entry`
WHERE voucher_type = %s and voucher_no = %s
and item_code = %s and voucher_detail_no = %s
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 08a06b19b4..7f3e0cf4c2 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -11,6 +11,8 @@ from frappe.core.utils import get_parent_doc
from frappe.email.inbox import link_communication_to_document
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Now
from frappe.utils import date_diff, get_datetime, now_datetime, time_diff_in_seconds
from frappe.utils.user import is_website_user
@@ -190,15 +192,17 @@ def auto_close_tickets():
frappe.db.get_value("Support Settings", "Support Settings", "close_issue_after_days") or 7
)
- issues = frappe.db.sql(
- """ select name from tabIssue where status='Replied' and
- modified
+
+ Sapcon Instruments Pvt Ltd
+
+
+ Level wise BOM Cost Updation and Performance Enhancement #31072
+
+