diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index e5b942fb6ef..d6e07ab31e0 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -8,7 +8,11 @@ from frappe.model.document import Document
from frappe.utils import flt, getdate, nowdate, today
import erpnext
-from erpnext.accounts.utils import get_outstanding_invoices, reconcile_against_document
+from erpnext.accounts.utils import (
+ get_outstanding_invoices,
+ reconcile_against_document,
+ update_reference_in_payment_entry,
+)
from erpnext.controllers.accounts_controller import get_advance_payment_entries
@@ -190,6 +194,23 @@ class PaymentReconciliation(Document):
inv.currency = entry.get("currency")
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
+ def get_difference_amount(self, allocated_entry):
+ if allocated_entry.get("reference_type") != "Payment Entry":
+ return
+
+ dr_or_cr = (
+ "credit_in_account_currency"
+ if erpnext.get_party_account_type(self.party_type) == "Receivable"
+ else "debit_in_account_currency"
+ )
+
+ row = self.get_payment_details(allocated_entry, dr_or_cr)
+
+ doc = frappe.get_doc(allocated_entry.reference_type, allocated_entry.reference_name)
+ update_reference_in_payment_entry(row, doc, do_not_save=True)
+
+ return doc.difference_amount
+
@frappe.whitelist()
def allocate_entries(self, args):
self.validate_entries()
@@ -205,12 +226,16 @@ class PaymentReconciliation(Document):
res = self.get_allocated_entry(pay, inv, pay["amount"])
inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount"))
pay["amount"] = 0
+
+ res.difference_amount = self.get_difference_amount(res)
+
if pay.get("amount") == 0:
entries.append(res)
break
elif inv.get("outstanding_amount") == 0:
entries.append(res)
continue
+
else:
break
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index b8500270d1a..6458756957d 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -1572,7 +1572,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2022-03-22 13:00:24.166684",
+ "modified": "2022-09-27 13:00:24.166684",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 59c7c843edf..6f1434d429b 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -239,14 +239,14 @@ class POSInvoice(SalesInvoice):
frappe.bold(d.warehouse),
frappe.bold(d.qty),
)
- if flt(available_stock) <= 0:
+ if is_stock_item and flt(available_stock) <= 0:
frappe.throw(
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
d.idx, item_code, warehouse
),
title=_("Item Unavailable"),
)
- elif flt(available_stock) < flt(d.qty):
+ elif is_stock_item and flt(available_stock) < flt(d.qty):
frappe.throw(
_(
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
@@ -634,11 +634,12 @@ def get_stock_availability(item_code, warehouse):
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
return bin_qty - pos_sales_qty, is_stock_item
else:
- is_stock_item = False
+ is_stock_item = True
if frappe.db.exists("Product Bundle", item_code):
return get_bundle_availability(item_code, warehouse), is_stock_item
else:
- # Is a service item
+ is_stock_item = False
+ # Is a service item or non_stock item
return 0, is_stock_item
@@ -652,7 +653,9 @@ def get_bundle_availability(bundle_item_code, warehouse):
available_qty = item_bin_qty - item_pos_reserved_qty
max_available_bundles = available_qty / item.qty
- if bundle_bin_qty > max_available_bundles:
+ if bundle_bin_qty > max_available_bundles and frappe.get_value(
+ "Item", item.item_code, "is_stock_item"
+ ):
bundle_bin_qty = max_available_bundles
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
@@ -744,3 +747,7 @@ def add_return_modes(doc, pos_profile):
]:
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
append_payment(payment_mode[0])
+
+
+def on_doctype_update():
+ frappe.db.add_index("POS Invoice", ["return_against"])
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index 82705a9cea4..0da44a464e7 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -25,7 +25,7 @@
-
| {{ _("Date") }} | diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index fc5b94284ef..ff3d5950808 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -513,7 +513,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 18b0636beca..60611b0edbb 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1549,6 +1549,37 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi.save() self.assertEqual(pi.items[0].conversion_factor, 1000) + def test_batch_expiry_for_purchase_invoice(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + item = self.make_item( + "_Test Batch Item For Return Check", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBIRC.#####", + }, + ) + + pi = make_purchase_invoice( + qty=1, + item_code=item.name, + update_stock=True, + ) + + pi.load_from_db() + batch_no = pi.items[0].batch_no + self.assertTrue(batch_no) + + frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1)) + + return_pi = make_return_doc(pi.doctype, pi.name) + return_pi.save().submit() + + self.assertTrue(return_pi.docstatus == 1) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index bbbaa67c8e9..cd3434a7756 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -651,7 +651,6 @@ "hide_days": 1, "hide_seconds": 1, "label": "Ignore Pricing Rule", - "no_copy": 1, "print_hide": 1 }, { @@ -2046,7 +2045,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-07-11 17:43:56.435382", + "modified": "2022-09-16 17:44:22.227332", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 6a3b38ae7b5..98c81086ab2 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -280,9 +280,9 @@ def get_conditions(filters): or filters.get("party") or filters.get("group_by") in ["Group by Account", "Group by Party"] ): - conditions.append("posting_date >=%(from_date)s") + conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')") - conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')") + conditions.append("(posting_date <=%(to_date)s)") if filters.get("project"): conditions.append("project in %(project)s") diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index 9d566785416..cd5f3667071 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -155,7 +155,6 @@ def adjust_account(data, period_list, consolidated=False): for d in data: for period in period_list: key = period if consolidated else period.key - d[key] = totals[d["account"]] d["total"] = totals[d["account"]] return data diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index c987231fe17..dd9c0736128 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -19,14 +19,19 @@ def execute(filters=None): return _execute(filters) -def _execute(filters=None, additional_table_columns=None, additional_query_columns=None): +def _execute( + filters=None, + additional_table_columns=None, + additional_query_columns=None, + additional_conditions=None, +): if not filters: filters = {} columns = get_columns(additional_table_columns, filters) company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") - item_list = get_items(filters, additional_query_columns) + item_list = get_items(filters, additional_query_columns, additional_conditions) if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) @@ -328,7 +333,7 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters): +def get_conditions(filters, additional_conditions=None): conditions = "" for opts in ( @@ -341,6 +346,9 @@ def get_conditions(filters): if filters.get(opts[0]): conditions += opts[1] + if additional_conditions: + conditions += additional_conditions + if filters.get("mode_of_payment"): conditions += """ and exists(select name from `tabSales Invoice Payment` where parent=`tabSales Invoice`.name @@ -376,8 +384,8 @@ def get_group_by_conditions(filters, doctype): return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by"))) -def get_items(filters, additional_query_columns): - conditions = get_conditions(filters) +def get_items(filters, additional_query_columns, additional_conditions=None): + conditions = get_conditions(filters, additional_conditions) if additional_query_columns: additional_query_columns = ", " + ", ".join(additional_query_columns) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 6bd08ad837a..6d2cd8ed411 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -172,6 +172,7 @@ def get_rootwise_opening_balances(filters, report_type): query_filters = { "company": filters.company, "from_date": filters.from_date, + "to_date": filters.to_date, "report_type": report_type, "year_start_date": filters.year_start_date, "project": filters.project, @@ -200,7 +201,7 @@ def get_rootwise_opening_balances(filters, report_type): where company=%(company)s {additional_conditions} - and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') + and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) and account in (select name from `tabAccount` where report_type=%(report_type)s) and is_cancelled = 0 group by account""".format( diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index 869f6aaf942..7ef2d22e309 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -106,12 +106,17 @@ def get_opening_balances(filters): where company=%(company)s and is_cancelled=0 and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' - and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') + and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) {account_filter} group by party""".format( account_filter=account_filter ), - {"company": filters.company, "from_date": filters.from_date, "party_type": filters.party_type}, + { + "company": filters.company, + "from_date": filters.from_date, + "to_date": filters.to_date, + "party_type": filters.party_type, + }, as_dict=True, ) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 239f4988303..c5bfcd57738 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -439,7 +439,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1170,7 +1169,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-04-26 12:16:38.694276", + "modified": "2022-09-16 17:45:04.954055", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a084ae5fe61..ffd021773c9 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -193,16 +193,16 @@ class BuyingController(StockController, Subcontracting): if self.meta.get_field("base_in_words"): if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled(): - amount = self.base_rounded_total + amount = abs(self.base_rounded_total) else: - amount = self.base_grand_total + amount = abs(self.base_grand_total) self.base_in_words = money_in_words(amount, self.company_currency) if self.meta.get_field("in_words"): if self.meta.get_field("rounded_total") and not self.is_rounded_total_disabled(): - amount = self.rounded_total + amount = abs(self.rounded_total) else: - amount = self.grand_total + amount = abs(self.grand_total) self.in_words = money_in_words(amount, self.currency) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 0556d40a4b8..2f009fd494f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -478,7 +478,6 @@ scheduler_events = { ], "hourly": [ "erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails", - "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details", "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization", "erpnext.projects.doctype.project.project.hourly_reminder", @@ -487,6 +486,7 @@ scheduler_events = { "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", ], "hourly_long": [ + "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries", "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", ], diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index c6f5bf05891..da94d96adb0 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -181,7 +181,6 @@ def add_data( total_l += 1 elif status == "Half Day": total_p += 0.5 - total_a += 0.5 total_l += 0.5 elif not status: total_um += 1 diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a0da7275eb7..8bf633b145e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -829,6 +829,7 @@ def get_exploded_items(item_details, company, bom_no, include_non_stock_items, p .select( (IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"), item.item_name, + item.name.as_("item_code"), bei.description, bei.stock_uom, item.min_order_qty, diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 9b0c8382c53..a27c42b109e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -557,37 +557,52 @@ erpnext.work_order = { if(!frm.doc.skip_transfer){ // If "Material Consumption is check in Manufacturing Settings, allow Material Consumption - if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing)) - && frm.doc.status != 'Stopped') { - frm.has_finish_btn = true; + if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') { + if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) { + frm.has_finish_btn = true; - if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { - // Only show "Material Consumption" when required_qty > consumed_qty - var counter = 0; - var tbl = frm.doc.required_items || []; - var tbl_lenght = tbl.length; - for (var i = 0, len = tbl_lenght; i < len; i++) { - let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; - if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { - counter += 1; + if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { + // Only show "Material Consumption" when required_qty > consumed_qty + var counter = 0; + var tbl = frm.doc.required_items || []; + var tbl_lenght = tbl.length; + for (var i = 0, len = tbl_lenght; i < len; i++) { + let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; + if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { + counter += 1; + } + } + if (counter > 0) { + var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { + const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; + erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); + }); + consumption_btn.addClass('btn-primary'); } } - if (counter > 0) { - var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { - const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; - erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); - }); - consumption_btn.addClass('btn-primary'); + + var finish_btn = frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + + if(doc.material_transferred_for_manufacturing>=doc.qty) { + // all materials transferred for manufacturing, make this primary + finish_btn.addClass('btn-primary'); } - } + } else { + frappe.db.get_doc("Manufacturing Settings").then((doc) => { + let allowance_percentage = doc.overproduction_percentage_for_work_order; - var finish_btn = frm.add_custom_button(__('Finish'), function() { - erpnext.work_order.make_se(frm, 'Manufacture'); - }); + if (allowance_percentage > 0) { + let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); - if(doc.material_transferred_for_manufacturing>=doc.qty) { - // all materials transferred for manufacturing, make this primary - finish_btn.addClass('btn-primary'); + if ((flt(doc.produced_qty) < allowed_qty)) { + frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + } + } + }); } } } else { diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py index 3fe2198966c..70a1850fd0f 100644 --- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py +++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py @@ -64,22 +64,21 @@ def get_columns(filters): def get_data(filters): - cond = "1=1" + wo = frappe.qb.DocType("Work Order") + query = ( + frappe.qb.from_(wo) + .select(wo.name.as_("work_order"), wo.qty, wo.produced_qty, wo.production_item, wo.bom_no) + .where((wo.produced_qty > wo.qty) & (wo.docstatus == 1)) + ) if filters.get("bom_no") and not filters.get("work_order"): - cond += " and bom_no = '%s'" % filters.get("bom_no") + query = query.where(wo.bom_no == filters.get("bom_no")) if filters.get("work_order"): - cond += " and name = '%s'" % filters.get("work_order") + query = query.where(wo.name == filters.get("work_order")) results = [] - for d in frappe.db.sql( - """ select name as work_order, qty, produced_qty, production_item, bom_no - from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format( - cond - ), - as_dict=1, - ): + for d in query.run(as_dict=True): results.append(d) for data in frappe.get_all( @@ -95,16 +94,17 @@ def get_data(filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_work_orders(doctype, txt, searchfield, start, page_len, filters): - cond = "1=1" - if filters.get("bom_no"): - cond += " and bom_no = '%s'" % filters.get("bom_no") - - return frappe.db.sql( - """select name from `tabWork Order` - where name like %(name)s and {0} and produced_qty > qty and docstatus = 1 - order by name limit {1}, {2}""".format( - cond, start, page_len - ), - {"name": "%%%s%%" % txt}, - as_list=1, + wo = frappe.qb.DocType("Work Order") + query = ( + frappe.qb.from_(wo) + .select(wo.name) + .where((wo.name.like(f"{txt}%")) & (wo.produced_qty > wo.qty) & (wo.docstatus == 1)) + .orderby(wo.name) + .limit(page_len) + .offset(start) ) + + if filters.get("bom_no"): + query = query.where(wo.bom_no == filters.get("bom_no")) + + return query.run(as_list=True) diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 7500744c228..d3bce831551 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -96,38 +96,39 @@ class ForecastingReport(ExponentialSmoothingForecast): value["avg"] = flt(sum(list_of_period_value)) / flt(sum(total_qty)) def get_data_for_forecast(self): - cond = "" - if self.filters.item_code: - cond = " AND soi.item_code = %s" % (frappe.db.escape(self.filters.item_code)) - - warehouses = [] - if self.filters.warehouse: - warehouses = get_child_warehouses(self.filters.warehouse) - cond += " AND soi.warehouse in ({})".format(",".join(["%s"] * len(warehouses))) - - input_data = [self.filters.from_date, self.filters.company] - if warehouses: - input_data.extend(warehouses) + parent = frappe.qb.DocType(self.doctype) + child = frappe.qb.DocType(self.child_doctype) date_field = "posting_date" if self.doctype == "Delivery Note" else "transaction_date" - return frappe.db.sql( - """ - SELECT - so.{date_field} as posting_date, soi.item_code, soi.warehouse, - soi.item_name, soi.stock_qty as qty, soi.base_amount as amount - FROM - `tab{doc}` so, `tab{child_doc}` soi - WHERE - so.docstatus = 1 AND so.name = soi.parent AND - so.{date_field} < %s AND so.company = %s {cond} - """.format( - doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond - ), - tuple(input_data), - as_dict=1, + query = ( + frappe.qb.from_(parent) + .from_(child) + .select( + parent[date_field].as_("posting_date"), + child.item_code, + child.warehouse, + child.item_name, + child.stock_qty.as_("qty"), + child.base_amount.as_("amount"), + ) + .where( + (parent.docstatus == 1) + & (parent.name == child.parent) + & (parent[date_field] < self.filters.from_date) + & (parent.company == self.filters.company) + ) ) + if self.filters.item_code: + query = query.where(child.item_code == self.filters.item_code) + + if self.filters.warehouse: + warehouses = get_child_warehouses(self.filters.warehouse) or [] + query = query.where(child.warehouse.isin(warehouses)) + + return query.run(as_dict=True) + def prepare_final_data(self): self.data = [] diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py index b10e6434223..ce8f4f35a3f 100644 --- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py @@ -5,6 +5,7 @@ from typing import Dict, List, Tuple import frappe from frappe import _ +from frappe.query_builder.functions import Sum Filters = frappe._dict Row = frappe._dict @@ -14,15 +15,50 @@ QueryArgs = Dict[str, str] def execute(filters: Filters) -> Tuple[Columns, Data]: + filters = frappe._dict(filters or {}) columns = get_columns() data = get_data(filters) return columns, data def get_data(filters: Filters) -> Data: - query_args = get_query_args(filters) - data = run_query(query_args) + wo = frappe.qb.DocType("Work Order") + se = frappe.qb.DocType("Stock Entry") + + query = ( + frappe.qb.from_(wo) + .inner_join(se) + .on(wo.name == se.work_order) + .select( + wo.name, + wo.status, + wo.production_item, + wo.qty, + wo.produced_qty, + wo.process_loss_qty, + (wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"), + Sum(se.total_incoming_value).as_("total_fg_value"), + Sum(se.total_outgoing_value).as_("total_rm_value"), + ) + .where( + (wo.process_loss_qty > 0) + & (wo.company == filters.company) + & (se.docstatus == 1) + & (se.posting_date.between(filters.from_date, filters.to_date)) + ) + .groupby(se.work_order) + ) + + if "item" in filters: + query.where(wo.production_item == filters.item) + + if "work_order" in filters: + query.where(wo.name == filters.work_order) + + data = query.run(as_dict=True) + update_data_with_total_pl_value(data) + return data @@ -67,54 +103,7 @@ def get_columns() -> Columns: ] -def get_query_args(filters: Filters) -> QueryArgs: - query_args = {} - query_args.update(filters) - query_args.update(get_filter_conditions(filters)) - return query_args - - -def run_query(query_args: QueryArgs) -> Data: - return frappe.db.sql( - """ - SELECT - wo.name, wo.status, wo.production_item, wo.qty, - wo.produced_qty, wo.process_loss_qty, - (wo.produced_qty - wo.process_loss_qty) as actual_produced_qty, - sum(se.total_incoming_value) as total_fg_value, - sum(se.total_outgoing_value) as total_rm_value - FROM - `tabWork Order` wo INNER JOIN `tabStock Entry` se - ON wo.name=se.work_order - WHERE - process_loss_qty > 0 - AND wo.company = %(company)s - AND se.docstatus = 1 - AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s - {item_filter} - {work_order_filter} - GROUP BY - se.work_order - """.format( - **query_args - ), - query_args, - as_dict=1, - ) - - def update_data_with_total_pl_value(data: Data) -> None: for row in data: value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"] row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg - - -def get_filter_conditions(filters: Filters) -> QueryArgs: - filter_conditions = dict(item_filter="", work_order_filter="") - if "item" in filters: - production_item = filters.get("item") - filter_conditions.update({"item_filter": f"AND wo.production_item='{production_item}'"}) - if "work_order" in filters: - work_order_name = filters.get("work_order") - filter_conditions.update({"work_order_filter": f"AND wo.name='{work_order_name}'"}) - return filter_conditions diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index 140488820a5..16c25ce7e6d 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -4,42 +4,10 @@ import frappe from frappe import _ +from pypika import Order from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses -# and bom_no is not null and bom_no !='' - -mapper = { - "Sales Order": { - "fields": """ item_code as production_item, item_name as production_item_name, stock_uom, - stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse, - `tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """, - "filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty - and `tabSales Order`.per_delivered < 100.0""", - }, - "Material Request": { - "fields": """ item_code as production_item, item_name as production_item_name, stock_uom, - stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse, - `tabMaterial Request Item`.schedule_date """, - "filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100 - and `tabMaterial Request`.material_request_type = 'Manufacture' """, - }, - "Work Order": { - "fields": """ production_item, item_name as production_item_name, planned_start_date, - stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """, - "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')", - }, -} - -order_mapper = { - "Sales Order": { - "Delivery Date": "`tabSales Order Item`.delivery_date asc", - "Total Amount": "`tabSales Order`.base_grand_total desc", - }, - "Material Request": {"Required Date": "`tabMaterial Request Item`.schedule_date asc"}, - "Work Order": {"Planned Start Date": "planned_start_date asc"}, -} - def execute(filters=None): return ProductionPlanReport(filters).execute_report() @@ -63,40 +31,78 @@ class ProductionPlanReport(object): return self.columns, self.data def get_open_orders(self): - doctype = ( - "`tabWork Order`" - if self.filters.based_on == "Work Order" - else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on) - ) + doctype, order_by = self.filters.based_on, self.filters.order_by - filters = mapper.get(self.filters.based_on)["filters"] - filters = self.prepare_other_conditions(filters, self.filters.based_on) - order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by]) + parent = frappe.qb.DocType(doctype) + query = None - self.orders = frappe.db.sql( - """ SELECT {fields} from {doctype} - WHERE {filters} {order_by}""".format( - doctype=doctype, - filters=filters, - order_by=order_by, - fields=mapper.get(self.filters.based_on)["fields"], - ), - tuple(self.filters.docnames), - as_dict=1, - ) + if doctype == "Work Order": + query = ( + frappe.qb.from_(parent) + .select( + parent.production_item, + parent.item_name.as_("production_item_name"), + parent.planned_start_date, + parent.stock_uom, + parent.qty.as_("qty_to_manufacture"), + parent.name, + parent.bom_no, + parent.fg_warehouse.as_("warehouse"), + ) + .where(parent.status.notin(["Completed", "Stopped"])) + ) - def prepare_other_conditions(self, filters, doctype): - if self.filters.docnames: - field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype) - filters += " and %s in (%s)" % (field, ",".join(["%s"] * len(self.filters.docnames))) + if order_by == "Planned Start Date": + query = query.orderby(parent.planned_start_date, order=Order.asc) - if doctype != "Work Order": - filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype) + if self.filters.docnames: + query = query.where(parent.name.isin(self.filters.docnames)) + + else: + child = frappe.qb.DocType(f"{doctype} Item") + query = ( + frappe.qb.from_(parent) + .from_(child) + .select( + child.bom_no, + child.stock_uom, + child.warehouse, + child.parent.as_("name"), + child.item_code.as_("production_item"), + child.stock_qty.as_("qty_to_manufacture"), + child.item_name.as_("production_item_name"), + ) + .where(parent.name == child.parent) + ) + + if self.filters.docnames: + query = query.where(child.parent.isin(self.filters.docnames)) + + if doctype == "Sales Order": + query = query.select( + child.delivery_date, + parent.base_grand_total, + ).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0)) + + if order_by == "Delivery Date": + query = query.orderby(child.delivery_date, order=Order.asc) + elif order_by == "Total Amount": + query = query.orderby(parent.base_grand_total, order=Order.desc) + + elif doctype == "Material Request": + query = query.select(child.schedule_date,).where( + (parent.per_ordered < 100) & (parent.material_request_type == "Manufacture") + ) + + if order_by == "Required Date": + query = query.orderby(child.schedule_date, order=Order.asc) + + query = query.where(parent.docstatus == 1) if self.filters.company: - filters += " and `tab%s`.company = %s" % (doctype, frappe.db.escape(self.filters.company)) + query = query.where(parent.company == self.filters.company) - return filters + self.orders = query.run(as_dict=True) def get_raw_materials(self): if not self.orders: @@ -134,29 +140,29 @@ class ProductionPlanReport(object): bom_nos.append(bom_no) - bom_doctype = ( + bom_item_doctype = ( "BOM Explosion Item" if self.filters.include_subassembly_raw_materials else "BOM Item" ) - qty_field = ( - "qty_consumed_per_unit" - if self.filters.include_subassembly_raw_materials - else "(bom_item.qty / bom.quantity)" - ) + bom = frappe.qb.DocType("BOM") + bom_item = frappe.qb.DocType(bom_item_doctype) - raw_materials = frappe.db.sql( - """ SELECT bom_item.parent, bom_item.item_code, - bom_item.item_name as raw_material_name, {0} as required_qty_per_unit - FROM - `tabBOM` as bom, `tab{1}` as bom_item - WHERE - bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1 - """.format( - qty_field, bom_doctype, ",".join(["%s"] * len(bom_nos)) - ), - tuple(bom_nos), - as_dict=1, - ) + if self.filters.include_subassembly_raw_materials: + qty_field = bom_item.qty_consumed_per_unit + else: + qty_field = bom_item.qty / bom.quantity + + raw_materials = ( + frappe.qb.from_(bom) + .from_(bom_item) + .select( + bom_item.parent, + bom_item.item_code, + bom_item.item_name.as_("raw_material_name"), + qty_field.as_("required_qty_per_unit"), + ) + .where((bom_item.parent.isin(bom_nos)) & (bom_item.parent == bom.name) & (bom.docstatus == 1)) + ).run(as_dict=True) if not raw_materials: return diff --git a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py index c6b7e58d656..d2e3e4081c2 100644 --- a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py +++ b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py @@ -3,6 +3,7 @@ import frappe +from frappe.query_builder.functions import IfNull from frappe.utils import cint @@ -16,70 +17,70 @@ def execute(filters=None): def get_item_list(wo_list, filters): out = [] - # Add a row for each item/qty - for wo_details in wo_list: - desc = frappe.db.get_value("BOM", wo_details.bom_no, "description") + if wo_list: + bin = frappe.qb.DocType("Bin") + bom = frappe.qb.DocType("BOM") + bom_item = frappe.qb.DocType("BOM Item") - for wo_item_details in frappe.db.get_values( - "Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1 - ): + # Add a row for each item/qty + for wo_details in wo_list: + desc = frappe.db.get_value("BOM", wo_details.bom_no, "description") - item_list = frappe.db.sql( - """SELECT - bom_item.item_code as item_code, - ifnull(ledger.actual_qty*bom.quantity/bom_item.stock_qty,0) as build_qty - FROM - `tabBOM` as bom, `tabBOM Item` AS bom_item - LEFT JOIN `tabBin` AS ledger - ON bom_item.item_code = ledger.item_code - AND ledger.warehouse = ifnull(%(warehouse)s,%(filterhouse)s) - WHERE - bom.name = bom_item.parent - and bom_item.item_code = %(item_code)s - and bom.name = %(bom)s - GROUP BY - bom_item.item_code""", - { - "bom": wo_details.bom_no, - "warehouse": wo_item_details.source_warehouse, - "filterhouse": filters.warehouse, - "item_code": wo_item_details.item_code, - }, - as_dict=1, - ) + for wo_item_details in frappe.db.get_values( + "Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1 + ): + item_list = ( + frappe.qb.from_(bom) + .from_(bom_item) + .left_join(bin) + .on( + (bom_item.item_code == bin.item_code) + & (bin.warehouse == IfNull(wo_item_details.source_warehouse, filters.warehouse)) + ) + .select( + bom_item.item_code.as_("item_code"), + IfNull(bin.actual_qty * bom.quantity / bom_item.stock_qty, 0).as_("build_qty"), + ) + .where( + (bom.name == bom_item.parent) + & (bom_item.item_code == wo_item_details.item_code) + & (bom.name == wo_details.bom_no) + ) + .groupby(bom_item.item_code) + ).run(as_dict=1) - stock_qty = 0 - count = 0 - buildable_qty = wo_details.qty - for item in item_list: - count = count + 1 - if item.build_qty >= (wo_details.qty - wo_details.produced_qty): - stock_qty = stock_qty + 1 - elif buildable_qty >= item.build_qty: - buildable_qty = item.build_qty + stock_qty = 0 + count = 0 + buildable_qty = wo_details.qty + for item in item_list: + count = count + 1 + if item.build_qty >= (wo_details.qty - wo_details.produced_qty): + stock_qty = stock_qty + 1 + elif buildable_qty >= item.build_qty: + buildable_qty = item.build_qty - if count == stock_qty: - build = "Y" - else: - build = "N" + if count == stock_qty: + build = "Y" + else: + build = "N" - row = frappe._dict( - { - "work_order": wo_details.name, - "status": wo_details.status, - "req_items": cint(count), - "instock": stock_qty, - "description": desc, - "source_warehouse": wo_item_details.source_warehouse, - "item_code": wo_item_details.item_code, - "bom_no": wo_details.bom_no, - "qty": wo_details.qty, - "buildable_qty": buildable_qty, - "ready_to_build": build, - } - ) + row = frappe._dict( + { + "work_order": wo_details.name, + "status": wo_details.status, + "req_items": cint(count), + "instock": stock_qty, + "description": desc, + "source_warehouse": wo_item_details.source_warehouse, + "item_code": wo_item_details.item_code, + "bom_no": wo_details.bom_no, + "qty": wo_details.qty, + "buildable_qty": buildable_qty, + "ready_to_build": build, + } + ) - out.append(row) + out.append(row) return out diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 9292fc1cfec..45ceb1566c6 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -894,6 +894,7 @@ class GSPConnector: return self.e_invoice_settings.auth_token def make_request(self, request_type, url, headers=None, data=None): + res = None try: if request_type == "post": res = make_post_request(url, headers=headers, data=data) diff --git a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.js b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.js index 3a0f0c966d6..d5e832a83f4 100644 --- a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.js +++ b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.js @@ -15,12 +15,6 @@ filters = filters.concat({ "placeholder":"Company GSTIN", "options": [""], "width": "80" -}, { - "fieldname":"invoice_type", - "label": __("Invoice Type"), - "fieldtype": "Select", - "placeholder":"Invoice Type", - "options": ["", "Regular", "SEZ", "Export", "Deemed Export"] }); // Handle company on change diff --git a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py index bb1843f1bd9..aef69b17868 100644 --- a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py +++ b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py @@ -5,31 +5,48 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import _execute +def get_conditions(filters, additional_query_columns): + conditions = "" + + for opts in additional_query_columns: + if filters.get(opts): + conditions += f" and {opts}=%({opts})s" + + return conditions + + def execute(filters=None): + additional_table_columns = [ + dict(fieldtype="Data", label="Customer GSTIN", fieldname="customer_gstin", width=120), + dict( + fieldtype="Data", label="Billing Address GSTIN", fieldname="billing_address_gstin", width=140 + ), + dict(fieldtype="Data", label="Company GSTIN", fieldname="company_gstin", width=120), + dict(fieldtype="Data", label="Place of Supply", fieldname="place_of_supply", width=120), + dict(fieldtype="Data", label="Reverse Charge", fieldname="reverse_charge", width=120), + dict(fieldtype="Data", label="GST Category", fieldname="gst_category", width=120), + dict(fieldtype="Data", label="Export Type", fieldname="export_type", width=120), + dict(fieldtype="Data", label="E-Commerce GSTIN", fieldname="ecommerce_gstin", width=130), + dict(fieldtype="Data", label="HSN Code", fieldname="gst_hsn_code", width=120), + ] + + additional_query_columns = [ + "customer_gstin", + "billing_address_gstin", + "company_gstin", + "place_of_supply", + "reverse_charge", + "gst_category", + "export_type", + "ecommerce_gstin", + "gst_hsn_code", + ] + + additional_conditions = get_conditions(filters, additional_query_columns) + return _execute( filters, - additional_table_columns=[ - dict(fieldtype="Data", label="Customer GSTIN", fieldname="customer_gstin", width=120), - dict( - fieldtype="Data", label="Billing Address GSTIN", fieldname="billing_address_gstin", width=140 - ), - dict(fieldtype="Data", label="Company GSTIN", fieldname="company_gstin", width=120), - dict(fieldtype="Data", label="Place of Supply", fieldname="place_of_supply", width=120), - dict(fieldtype="Data", label="Reverse Charge", fieldname="reverse_charge", width=120), - dict(fieldtype="Data", label="GST Category", fieldname="gst_category", width=120), - dict(fieldtype="Data", label="Export Type", fieldname="export_type", width=120), - dict(fieldtype="Data", label="E-Commerce GSTIN", fieldname="ecommerce_gstin", width=130), - dict(fieldtype="Data", label="HSN Code", fieldname="gst_hsn_code", width=120), - ], - additional_query_columns=[ - "customer_gstin", - "billing_address_gstin", - "company_gstin", - "place_of_supply", - "reverse_charge", - "gst_category", - "export_type", - "ecommerce_gstin", - "gst_hsn_code", - ], + additional_table_columns=additional_table_columns, + additional_query_columns=additional_query_columns, + additional_conditions=additional_conditions, ) diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 09fd310040c..e4d2065c13d 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -461,7 +461,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1, "show_days": 1, @@ -1174,7 +1173,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-06-15 20:35:32.635804", + "modified": "2022-09-16 17:44:43.221804", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index fc00fa6ea68..f77f494d800 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -544,7 +544,6 @@ "hide_days": 1, "hide_seconds": 1, "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1549,7 +1548,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-06-10 03:52:22.212953", + "modified": "2022-09-16 17:43:57.007441", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index da7576e08de..24375d8252d 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -660,7 +660,7 @@ erpnext.PointOfSale.Controller = class { } else { return; } - } else if (available_qty < qty_needed) { + } else if (is_stock_item && available_qty < qty_needed) { frappe.throw({ message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]), indicator: 'orange' @@ -694,7 +694,7 @@ erpnext.PointOfSale.Controller = class { callback(res) { if (!me.item_stock_map[item_code]) me.item_stock_map[item_code] = {}; - me.item_stock_map[item_code][warehouse] = res.message[0]; + me.item_stock_map[item_code][warehouse] = res.message; } }); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 1d720f7291a..f9b5bb2e452 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -242,13 +242,14 @@ erpnext.PointOfSale.ItemDetails = class { if (this.value) { me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => { me.item_stock_map = me.events.get_item_stock_map(); - const available_qty = me.item_stock_map[me.item_row.item_code][this.value]; + const available_qty = me.item_stock_map[me.item_row.item_code][this.value][0]; + const is_stock_item = Boolean(me.item_stock_map[me.item_row.item_code][this.value][1]); if (available_qty === undefined) { me.events.get_available_stock(me.item_row.item_code, this.value).then(() => { // item stock map is updated now reset warehouse me.warehouse_control.set_value(this.value); }) - } else if (available_qty === 0) { + } else if (available_qty === 0 && is_stock_item) { me.warehouse_control.set_value(''); const bold_item_code = me.item_row.item_code.bold(); const bold_warehouse = this.value.bold(); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index f9e934921d8..a8f907ed711 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -490,7 +490,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1336,7 +1335,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2022-06-10 03:52:04.197415", + "modified": "2022-09-16 17:46:17.701904", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 5cae3c50285..26a9ac0475c 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -404,7 +404,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1149,7 +1148,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-05-27 15:59:18.550583", + "modified": "2022-09-16 17:45:58.430132", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 4ee2d2dc7dc..e9c52436bc4 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1446,6 +1446,37 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(query[0].value, 0) + def test_batch_expiry_for_purchase_receipt(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + item = make_item( + "_Test Batch Item For Return Check", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBIRC.#####", + }, + ) + + pi = make_purchase_receipt( + qty=1, + item_code=item.name, + update_stock=True, + ) + + pi.load_from_db() + batch_no = pi.items[0].batch_no + self.assertTrue(batch_no) + + frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1)) + + return_pi = make_return_doc(pi.doctype, pi.name) + return_pi.save().submit() + + self.assertTrue(return_pi.docstatus == 1) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 174fef1084f..3dff7d2e003 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1015,8 +1015,8 @@ class StockEntry(StockController): # No work order could mean independent Manufacture entry, if so skip validation if self.work_order and self.fg_completed_qty > allowed_qty: frappe.throw( - _("For quantity {0} should not be greater than work order quantity {1}").format( - flt(self.fg_completed_qty), wo_qty + _("For quantity {0} should not be greater than allowed quantity {1}").format( + flt(self.fg_completed_qty), allowed_qty ) ) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 5c1da420e24..38eb80ad927 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -153,6 +153,9 @@ class StockLedgerEntry(Document): def validate_batch(self): if self.batch_no and self.voucher_type != "Stock Entry": + if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0: + return + expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date") if expiry_date: if getdate(self.posting_date) > getdate(expiry_date): diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py index 15f211127d7..46bcd94e278 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -4,6 +4,8 @@ import frappe from frappe import _ +from frappe.query_builder import Field +from frappe.query_builder.functions import Min, Timestamp from frappe.utils import add_days, getdate, today from six import iteritems @@ -29,7 +31,7 @@ def execute(filters=None): def get_unsync_date(filters): date = filters.from_date if not date: - date = frappe.db.sql(""" SELECT min(posting_date) from `tabStock Ledger Entry`""") + date = (frappe.qb.from_("Stock Ledger Entry").select(Min(Field("posting_date")))).run() date = date[0][0] if not date: @@ -55,22 +57,27 @@ def get_data(report_filters): result = [] voucher_wise_dict = {} - data = frappe.db.sql( - """ - SELECT - name, posting_date, posting_time, voucher_type, voucher_no, - stock_value_difference, stock_value, warehouse, item_code - FROM - `tabStock Ledger Entry` - WHERE - posting_date - = %s and company = %s - and is_cancelled = 0 - ORDER BY timestamp(posting_date, posting_time) asc, creation asc - """, - (from_date, report_filters.company), - as_dict=1, - ) + sle = frappe.qb.DocType("Stock Ledger Entry") + data = ( + frappe.qb.from_(sle) + .select( + sle.name, + sle.posting_date, + sle.posting_time, + sle.voucher_type, + sle.voucher_no, + sle.stock_value_difference, + sle.stock_value, + sle.warehouse, + sle.item_code, + ) + .where( + (sle.posting_date == from_date) + & (sle.company == report_filters.company) + & (sle.is_cancelled == 0) + ) + .orderby(Timestamp(sle.posting_date, sle.posting_time), sle.creation) + ).run(as_dict=True) for d in data: voucher_wise_dict.setdefault((d.item_code, d.warehouse), []).append(d) diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py index 15218e63a87..1b07f596c7b 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.py +++ b/erpnext/stock/report/item_price_stock/item_price_stock.py @@ -62,22 +62,28 @@ def get_data(filters, columns): def get_item_price_qty_data(filters): - conditions = "" - if filters.get("item_code"): - conditions += "where a.item_code=%(item_code)s" + item_price = frappe.qb.DocType("Item Price") + bin = frappe.qb.DocType("Bin") - item_results = frappe.db.sql( - """select a.item_code, a.item_name, a.name as price_list_name, - a.brand as brand, b.warehouse as warehouse, b.actual_qty as actual_qty - from `tabItem Price` a left join `tabBin` b - ON a.item_code = b.item_code - {conditions}""".format( - conditions=conditions - ), - filters, - as_dict=1, + query = ( + frappe.qb.from_(item_price) + .left_join(bin) + .on(item_price.item_code == bin.item_code) + .select( + item_price.item_code, + item_price.item_name, + item_price.name.as_("price_list_name"), + item_price.brand.as_("brand"), + bin.warehouse.as_("warehouse"), + bin.actual_qty.as_("actual_qty"), + ) ) + if filters.get("item_code"): + query = query.where(item_price.item_code == filters.get("item_code")) + + item_results = query.run(as_dict=True) + price_list_names = list(set(item.price_list_name for item in item_results)) buying_price_map = get_price_map(price_list_names, buying=1)
|---|