Merge pull request #32650 from barredterra/unset-contact-details
fix: unset contact details
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index afd5a59..0c03c55 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -2017,6 +2017,9 @@
update_address(
target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
)
+ update_address(
+ target_doc, "billing_address", "billing_address_display", source_doc.customer_address
+ )
if currency:
target_doc.currency = currency
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
index a95e0a9..f3acdc5 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
@@ -3,6 +3,7 @@
import frappe
+from dateutil import relativedelta
from frappe import _
from frappe.model.document import Document
from frappe.utils import date_diff, flt, get_first_day, get_last_day, getdate
@@ -49,7 +50,7 @@
start_date = getdate(start_date)
end_date = getdate(end_date)
- no_of_months = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + 1
+ no_of_months = relativedelta.relativedelta(end_date, start_date).months + 1
cost = plan.cost * no_of_months
# Adjust cost if start or end date is not month start or end
diff --git a/erpnext/accounts/report/payment_ledger/__init__.py b/erpnext/accounts/report/payment_ledger/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/__init__.py
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.js b/erpnext/accounts/report/payment_ledger/payment_ledger.js
new file mode 100644
index 0000000..9779844
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.js
@@ -0,0 +1,59 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+function get_filters() {
+ let filters = [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"period_start_date",
+ "label": __("Start Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1)
+ },
+ {
+ "fieldname":"period_end_date",
+ "label": __("End Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ },
+ {
+ "fieldname":"account",
+ "label": __("Account"),
+ "fieldtype": "MultiSelectList",
+ "options": "Account",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Account', txt, {
+ company: frappe.query_report.get_filter_value("company")
+ });
+ }
+ },
+ {
+ "fieldname":"voucher_no",
+ "label": __("Voucher No"),
+ "fieldtype": "Data",
+ "width": 100,
+ },
+ {
+ "fieldname":"against_voucher_no",
+ "label": __("Against Voucher No"),
+ "fieldtype": "Data",
+ "width": 100,
+ },
+
+ ]
+ return filters;
+}
+
+frappe.query_reports["Payment Ledger"] = {
+ "filters": get_filters()
+};
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.json b/erpnext/accounts/report/payment_ledger/payment_ledger.json
new file mode 100644
index 0000000..716329f
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2022-06-06 08:50:43.933708",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2022-06-06 08:50:43.933708",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Ledger",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Payment Ledger Entry",
+ "report_name": "Payment Ledger",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Auditor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py
new file mode 100644
index 0000000..e470c27
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py
@@ -0,0 +1,222 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from collections import OrderedDict
+
+import frappe
+from frappe import _, qb
+from frappe.query_builder import Criterion
+
+
+class PaymentLedger(object):
+ def __init__(self, filters=None):
+ self.filters = filters
+ self.columns, self.data = [], []
+ self.voucher_dict = OrderedDict()
+ self.voucher_amount = []
+ self.ple = qb.DocType("Payment Ledger Entry")
+
+ def init_voucher_dict(self):
+
+ if self.voucher_amount:
+ s = set()
+ # build a set of unique vouchers
+ for ple in self.voucher_amount:
+ key = (ple.voucher_type, ple.voucher_no, ple.party)
+ s.add(key)
+
+ # for each unique vouchers, initialize +/- list
+ for key in s:
+ self.voucher_dict[key] = frappe._dict(increase=list(), decrease=list())
+
+ # for each ple, using against voucher and amount, assign it to +/- list
+ # group by against voucher
+ for ple in self.voucher_amount:
+ against_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
+ target = None
+ if self.voucher_dict.get(against_key):
+ if ple.amount > 0:
+ target = self.voucher_dict.get(against_key).increase
+ else:
+ target = self.voucher_dict.get(against_key).decrease
+
+ # this if condition will lose unassigned ple entries(against_voucher doc doesn't have ple)
+ # need to somehow include the stray entries as well.
+ if target is not None:
+ entry = frappe._dict(
+ company=ple.company,
+ account=ple.account,
+ party_type=ple.party_type,
+ party=ple.party,
+ voucher_type=ple.voucher_type,
+ voucher_no=ple.voucher_no,
+ against_voucher_type=ple.against_voucher_type,
+ against_voucher_no=ple.against_voucher_no,
+ amount=ple.amount,
+ currency=ple.account_currency,
+ )
+
+ if self.filters.include_account_currency:
+ entry["amount_in_account_currency"] = ple.amount_in_account_currency
+
+ target.append(entry)
+
+ def build_data(self):
+ self.data.clear()
+
+ for value in self.voucher_dict.values():
+ voucher_data = []
+ if value.increase != []:
+ voucher_data.extend(value.increase)
+ if value.decrease != []:
+ voucher_data.extend(value.decrease)
+
+ if voucher_data:
+ # balance row
+ total = 0
+ total_in_account_currency = 0
+
+ for x in voucher_data:
+ total += x.amount
+ if self.filters.include_account_currency:
+ total_in_account_currency += x.amount_in_account_currency
+
+ entry = frappe._dict(
+ against_voucher_no="Outstanding:",
+ amount=total,
+ currency=voucher_data[0].currency,
+ )
+
+ if self.filters.include_account_currency:
+ entry["amount_in_account_currency"] = total_in_account_currency
+
+ voucher_data.append(entry)
+
+ # empty row
+ voucher_data.append(frappe._dict())
+ self.data.extend(voucher_data)
+
+ def build_conditions(self):
+ self.conditions = []
+
+ if self.filters.company:
+ self.conditions.append(self.ple.company == self.filters.company)
+
+ if self.filters.account:
+ self.conditions.append(self.ple.account.isin(self.filters.account))
+
+ if self.filters.period_start_date:
+ self.conditions.append(self.ple.posting_date.gte(self.filters.period_start_date))
+
+ if self.filters.period_end_date:
+ self.conditions.append(self.ple.posting_date.lte(self.filters.period_end_date))
+
+ if self.filters.voucher_no:
+ self.conditions.append(self.ple.voucher_no == self.filters.voucher_no)
+
+ if self.filters.against_voucher_no:
+ self.conditions.append(self.ple.against_voucher_no == self.filters.against_voucher_no)
+
+ def get_data(self):
+ ple = self.ple
+
+ self.build_conditions()
+
+ # fetch data from table
+ self.voucher_amount = (
+ qb.from_(ple)
+ .select(ple.star)
+ .where(ple.delinked == 0)
+ .where(Criterion.all(self.conditions))
+ .run(as_dict=True)
+ )
+
+ def get_columns(self):
+ options = None
+ self.columns.append(
+ dict(label=_("Company"), fieldname="company", fieldtype="data", options=options, width="100")
+ )
+
+ self.columns.append(
+ dict(label=_("Account"), fieldname="account", fieldtype="data", options=options, width="100")
+ )
+
+ self.columns.append(
+ dict(
+ label=_("Party Type"), fieldname="party_type", fieldtype="data", options=options, width="100"
+ )
+ )
+ self.columns.append(
+ dict(label=_("Party"), fieldname="party", fieldtype="data", options=options, width="100")
+ )
+ self.columns.append(
+ dict(
+ label=_("Voucher Type"),
+ fieldname="voucher_type",
+ fieldtype="data",
+ options=options,
+ width="100",
+ )
+ )
+ self.columns.append(
+ dict(
+ label=_("Voucher No"), fieldname="voucher_no", fieldtype="data", options=options, width="100"
+ )
+ )
+ self.columns.append(
+ dict(
+ label=_("Against Voucher Type"),
+ fieldname="against_voucher_type",
+ fieldtype="data",
+ options=options,
+ width="100",
+ )
+ )
+ self.columns.append(
+ dict(
+ label=_("Against Voucher No"),
+ fieldname="against_voucher_no",
+ fieldtype="data",
+ options=options,
+ width="100",
+ )
+ )
+ self.columns.append(
+ dict(
+ label=_("Amount"),
+ fieldname="amount",
+ fieldtype="Currency",
+ options="Company:company:default_currency",
+ width="100",
+ )
+ )
+
+ if self.filters.include_account_currency:
+ self.columns.append(
+ dict(
+ label=_("Amount in Account Currency"),
+ fieldname="amount_in_account_currency",
+ fieldtype="Currency",
+ options="currency",
+ width="100",
+ )
+ )
+ self.columns.append(
+ dict(label=_("Currency"), fieldname="currency", fieldtype="Currency", hidden=True)
+ )
+
+ def run(self):
+ self.get_columns()
+ self.get_data()
+
+ # initialize dictionary and group using against voucher
+ self.init_voucher_dict()
+
+ # convert dictionary to list and add balance rows
+ self.build_data()
+
+ return self.columns, self.data
+
+
+def execute(filters=None):
+ return PaymentLedger(filters).run()
diff --git a/erpnext/accounts/report/payment_ledger/test_payment_ledger.py b/erpnext/accounts/report/payment_ledger/test_payment_ledger.py
new file mode 100644
index 0000000..5ae9b87
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/test_payment_ledger.py
@@ -0,0 +1,65 @@
+import unittest
+
+import frappe
+from frappe import qb
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.report.payment_ledger.payment_ledger import execute
+
+
+class TestPaymentLedger(FrappeTestCase):
+ def setUp(self):
+ self.create_company()
+ self.cleanup()
+
+ def cleanup(self):
+ doctypes = []
+ doctypes.append(qb.DocType("GL Entry"))
+ doctypes.append(qb.DocType("Payment Ledger Entry"))
+ doctypes.append(qb.DocType("Sales Invoice"))
+ doctypes.append(qb.DocType("Payment Entry"))
+
+ for doctype in doctypes:
+ qb.from_(doctype).delete().where(doctype.company == self.company).run()
+
+ def create_company(self):
+ name = "Test Payment Ledger"
+ company = None
+ if frappe.db.exists("Company", name):
+ company = frappe.get_doc("Company", name)
+ else:
+ company = frappe.get_doc(
+ {
+ "doctype": "Company",
+ "company_name": name,
+ "country": "India",
+ "default_currency": "INR",
+ "create_chart_of_accounts_based_on": "Standard Template",
+ "chart_of_accounts": "Standard",
+ }
+ )
+ company = company.save()
+ self.company = company.name
+ self.cost_center = company.cost_center
+ self.warehouse = "All Warehouses" + " - " + company.abbr
+ self.income_account = company.default_income_account
+ self.expense_account = company.default_expense_account
+ self.debit_to = company.default_receivable_account
+
+ def test_unpaid_invoice_outstanding(self):
+ sinv = create_sales_invoice(
+ company=self.company,
+ debit_to=self.debit_to,
+ expense_account=self.expense_account,
+ cost_center=self.cost_center,
+ income_account=self.income_account,
+ warehouse=self.warehouse,
+ )
+ pe = get_payment_entry(sinv.doctype, sinv.name).save().submit()
+
+ filters = frappe._dict({"company": self.company})
+ columns, data = execute(filters=filters)
+ outstanding = [x for x in data if x.get("against_voucher_no") == "Outstanding:"]
+ self.assertEqual(outstanding[0].get("amount"), 0)
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index ff84991..580838e 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -385,6 +385,7 @@
if self.docstatus == 2:
return
+ self.flags.cost_updated = False
existing_bom_cost = self.total_cost
if self.docstatus == 1:
@@ -407,7 +408,11 @@
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
if not from_child_bom:
- frappe.msgprint(_("Cost Updated"), alert=True)
+ msg = "Cost Updated"
+ if not self.flags.cost_updated:
+ msg = "No changes in cost found"
+
+ frappe.msgprint(_(msg), alert=True)
def update_parent_cost(self):
if self.total_cost:
@@ -593,11 +598,16 @@
# not via doc event, table is not regenerated and needs updation
self.calculate_exploded_cost()
+ old_cost = self.total_cost
+
self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost
self.base_total_cost = (
self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
)
+ if self.total_cost != old_cost:
+ self.flags.cost_updated = True
+
def calculate_op_cost(self, update_hour_rate=False):
"""Update workstation rate and calculates totals"""
self.operating_cost = 0
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 27f3cc9..e34ac12 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -9,7 +9,10 @@
from frappe.tests.utils import FrappeTestCase
from frappe.utils import cstr, flt
-from erpnext.controllers.tests.test_subcontracting_controller import set_backflush_based_on
+from erpnext.controllers.tests.test_subcontracting_controller import (
+ make_stock_in_entry,
+ set_backflush_based_on,
+)
from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query, make_variant_bom
from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import (
update_cost_in_all_boms_in_test,
@@ -639,6 +642,28 @@
bom.submit()
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+ def test_bom_cost_update_flag(self):
+ rm_item = make_item(
+ properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
+ ).name
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+
+ from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+ bom = make_bom(item=fg_item, raw_materials=[rm_item])
+
+ create_stock_reconciliation(
+ item_code=rm_item, warehouse="_Test Warehouse - _TC", qty=100, rate=600
+ )
+
+ bom.load_from_db()
+ bom.update_cost()
+ self.assertTrue(bom.flags.cost_updated)
+
+ bom.load_from_db()
+ bom.update_cost()
+ self.assertFalse(bom.flags.cost_updated)
+
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index ed45106..fb94e8a 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -133,7 +133,7 @@
(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0}
)
and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1}
- order by jctl.to_time desc limit 1""".format(
+ order by jctl.to_time desc""".format(
extra_cond, validate_overlap_for
),
{
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index ac71141..4d2dab7 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -136,6 +136,45 @@
)
self.assertRaises(OverlapError, jc2.save)
+ def test_job_card_overlap_with_capacity(self):
+ wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
+
+ workstation = make_workstation(workstation_name=random_string(5)).name
+ frappe.db.set_value("Workstation", workstation, "production_capacity", 1)
+
+ jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
+ jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name})
+
+ jc1.workstation = workstation
+ jc1.append(
+ "time_logs",
+ {"from_time": "2021-01-01 00:00:00", "to_time": "2021-01-01 08:00:00", "completed_qty": 1},
+ )
+ jc1.save()
+
+ jc2.workstation = workstation
+
+ # add a new entry in same time slice
+ jc2.append(
+ "time_logs",
+ {"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1},
+ )
+ self.assertRaises(OverlapError, jc2.save)
+
+ frappe.db.set_value("Workstation", workstation, "production_capacity", 2)
+ jc2.load_from_db()
+
+ jc2.workstation = workstation
+
+ # add a new entry in same time slice
+ jc2.append(
+ "time_logs",
+ {"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1},
+ )
+
+ jc2.save()
+ self.assertTrue(jc2.name)
+
def test_job_card_multiple_materials_transfer(self):
"Test transferring RMs separately against Job Card with multiple RMs."
self.transfer_material_against = "Job Card"
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 4bb4dcc..000ee07 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -27,6 +27,7 @@
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
+from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.utilities.transaction_base import validate_uom_is_integer
@@ -648,13 +649,23 @@
else:
material_request = material_request_map[key]
+ conversion_factor = 1.0
+ if (
+ material_request_type == "Purchase"
+ and item_doc.purchase_uom
+ and item_doc.purchase_uom != item_doc.stock_uom
+ ):
+ conversion_factor = (
+ get_conversion_factor(item_doc.name, item_doc.purchase_uom).get("conversion_factor") or 1.0
+ )
+
# add item
material_request.append(
"items",
{
"item_code": item.item_code,
"from_warehouse": item.from_warehouse,
- "qty": item.quantity,
+ "qty": item.quantity / conversion_factor,
"schedule_date": schedule_date,
"warehouse": item.warehouse,
"sales_order": item.sales_order,
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 60e6398..c4ab0f8 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -806,6 +806,35 @@
self.assertEqual(pln.status, "Completed")
self.assertEqual(pln.po_items[0].produced_qty, 5)
+ def test_material_request_item_for_purchase_uom(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+ bom_item = make_item(
+ properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
+ ).name
+
+ if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
+ doc = frappe.get_doc("Item", bom_item)
+ doc.append("uoms", {"uom": "Nos", "conversion_factor": 10})
+ doc.save()
+
+ make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
+
+ pln = create_production_plan(
+ item_code=fg_item, planned_qty=10, ignore_existing_ordered_qty=1, stock_uom="_Test UOM 1"
+ )
+
+ pln.make_material_request()
+ for row in frappe.get_all(
+ "Material Request Item",
+ filters={"production_plan": pln.name},
+ fields=["item_code", "uom", "qty"],
+ ):
+ self.assertEqual(row.item_code, bom_item)
+ self.assertEqual(row.uom, "Nos")
+ self.assertEqual(row.qty, 1)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 1a309ba..b0082bd 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -28,7 +28,7 @@
},
"open_general_ledger": function(data) {
if (!data.account) return;
- var project = $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; })
+ let project = $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; });
frappe.route_options = {
"account": data.account,
@@ -37,7 +37,16 @@
"to_date": data.to_date || data.year_end_date,
"project": (project && project.length > 0) ? project[0].$input.val() : ""
};
- frappe.set_route("query-report", "General Ledger");
+
+ let report = "General Ledger";
+
+ if (["Payable", "Receivable"].includes(data.account_type)) {
+ report = data.account_type == "Payable" ? "Accounts Payable" : "Accounts Receivable";
+ frappe.route_options["party_account"] = data.account;
+ frappe.route_options["report_date"] = data.year_end_date;
+ }
+
+ frappe.set_route("query-report", report);
},
"tree": true,
"name_field": "account",
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 70ae085..6b42e4d 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -84,11 +84,12 @@
}
}
- 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'));
- }
+ if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
+ this.frm.add_custom_button(
+ __("Sales Order"),
+ this.frm.cscript["Make Sales Order"],
+ __("Create")
+ );
if(doc.status!=="Ordered") {
this.frm.add_custom_button(__('Set as Lost'), () => {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 36d5a6c..9dd28dc 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -842,6 +842,9 @@
update_address(
target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
)
+ update_address(
+ target_doc, "billing_address", "billing_address_display", source_doc.customer_address
+ )
update_taxes(
target_doc,
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 9fb3be5..b8c5187 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -13,6 +13,8 @@
import erpnext
from erpnext.stock.valuation import FIFOValuation, LIFOValuation
+BarcodeScanResult = Dict[str, Optional[str]]
+
class InvalidWarehouseCompany(frappe.ValidationError):
pass
@@ -552,7 +554,16 @@
@frappe.whitelist()
-def scan_barcode(search_value: str) -> Dict[str, Optional[str]]:
+def scan_barcode(search_value: str) -> BarcodeScanResult:
+ def set_cache(data: BarcodeScanResult):
+ frappe.cache().set_value(f"erpnext:barcode_scan:{search_value}", data, expires_in_sec=120)
+
+ def get_cache() -> Optional[BarcodeScanResult]:
+ if data := frappe.cache().get_value(f"erpnext:barcode_scan:{search_value}"):
+ return data
+
+ if scan_data := get_cache():
+ return scan_data
# search barcode no
barcode_data = frappe.db.get_value(
@@ -562,7 +573,9 @@
as_dict=True,
)
if barcode_data:
- return _update_item_info(barcode_data)
+ _update_item_info(barcode_data)
+ set_cache(barcode_data)
+ return barcode_data
# search serial no
serial_no_data = frappe.db.get_value(
@@ -572,7 +585,9 @@
as_dict=True,
)
if serial_no_data:
- return _update_item_info(serial_no_data)
+ _update_item_info(serial_no_data)
+ set_cache(serial_no_data)
+ return serial_no_data
# search batch no
batch_no_data = frappe.db.get_value(
@@ -582,7 +597,9 @@
as_dict=True,
)
if batch_no_data:
- return _update_item_info(batch_no_data)
+ _update_item_info(batch_no_data)
+ set_cache(batch_no_data)
+ return batch_no_data
return {}