diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index c801cfcbba6..06068238213 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -214,6 +214,7 @@ class Account(NestedSet):
if parent_value_changed:
doc.save()
+ @frappe.whitelist()
def convert_group_to_ledger(self):
if self.check_if_child_exists():
throw(_("Account with child nodes cannot be converted to ledger"))
@@ -224,6 +225,7 @@ class Account(NestedSet):
self.save()
return 1
+ @frappe.whitelist()
def convert_ledger_to_group(self):
if self.check_gle_exists():
throw(_("Account with existing transaction can not be converted to group."))
diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py
index df6cedd7cf8..63b5dbbd3e6 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py
@@ -39,6 +39,7 @@ class AccountingPeriod(Document):
frappe.throw(_("Accounting Period overlaps with {0}")
.format(existing_accounting_period[0].get("name")), OverlapError)
+ @frappe.whitelist()
def get_doctypes_for_closing(self):
docs_for_closing = []
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 76d82e73393..79f5596384c 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -12,6 +12,7 @@ form_grid_templates = {
}
class BankClearance(Document):
+ @frappe.whitelist()
def get_payment_entries(self):
if not (self.from_date and self.to_date):
frappe.throw(_("From Date and To Date are Mandatory"))
@@ -108,6 +109,7 @@ class BankClearance(Document):
row.update(d)
self.total_amount += flt(amount)
+ @frappe.whitelist()
def update_clearance_date(self):
clearance_date_updated = False
for d in self.get('payment_entries'):
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
index ad4ff9ee60a..3dbd6053441 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
@@ -532,43 +532,4 @@ frappe.ui.form.on("Bank Statement Import", {
`);
},
-
- show_missing_link_values(frm, missing_link_values) {
- let can_be_created_automatically = missing_link_values.every(
- (d) => d.has_one_mandatory_field
- );
-
- let html = missing_link_values
- .map((d) => {
- let doctype = d.doctype;
- let values = d.missing_values;
- return `
-
- | {{__("Suppliies made to Composition Taxable Persons")}} |
+ {{__("Supplies made to Composition Taxable Persons")}} |
{% for row in data.inter_sup.comp_details %}
{% if row %}
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index a49996d107e..a5dd5a2e094 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -172,7 +172,6 @@ class GSTR3BReport(Document):
self.json_output = frappe.as_json(self.report_dict)
def set_inward_nil_exempt(self, inward_nil_exempt):
-
self.report_dict["inward_sup"]["isup_details"][0]["inter"] = flt(inward_nil_exempt.get("gst").get("inter"), 2)
self.report_dict["inward_sup"]["isup_details"][0]["intra"] = flt(inward_nil_exempt.get("gst").get("intra"), 2)
self.report_dict["inward_sup"]["isup_details"][1]["inter"] = flt(inward_nil_exempt.get("non_gst").get("inter"), 2)
@@ -238,7 +237,6 @@ class GSTR3BReport(Document):
self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2)
def set_inter_state_supply(self, inter_state_supply):
-
osup_det = self.report_dict["sup_details"]["osup_det"]
for key, value in iteritems(inter_state_supply):
@@ -352,10 +350,18 @@ class GSTR3BReport(Document):
inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount,
i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
where p.docstatus = 1 and p.name = i.parent
+ and p.gst_category != 'Registered Composition'
and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and
month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s
group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ inward_nil_exempt += frappe.db.sql("""SELECT sum(base_net_total) as base_amount, gst_category, place_of_supply
+ FROM `tabPurchase Invoice`
+ WHERE docstatus = 1 and gst_category = 'Registered Composition'
+ and month(posting_date) = %s and year(posting_date) = %s
+ and company = %s and company_gstin = %s
+ group by place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+
inward_nil_exempt_details = {
"gst": {
"intra": 0.0,
@@ -369,9 +375,11 @@ class GSTR3BReport(Document):
for d in inward_nil_exempt:
if d.place_of_supply:
- if d.is_nil_exempt == 1 and state == d.place_of_supply.split("-")[1]:
+ if (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
+ and state == d.place_of_supply.split("-")[1]:
inward_nil_exempt_details["gst"]["intra"] += d.base_amount
- elif d.is_nil_exempt == 1 and state != d.place_of_supply.split("-")[1]:
+ elif (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
+ and state != d.place_of_supply.split("-")[1]:
inward_nil_exempt_details["gst"]["inter"] += d.base_amount
elif d.is_non_gst == 1 and state == d.place_of_supply.split("-")[1]:
inward_nil_exempt_details["non_gst"]["intra"] += d.base_amount
diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
index 023b4ed22bc..ef8af24c42a 100644
--- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
@@ -64,7 +64,7 @@ class TestGSTR3BReport(unittest.TestCase):
self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18),
self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18),
self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100),
- self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250)
+ self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250)
self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50)
self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50)
@@ -228,6 +228,19 @@ def create_purchase_invoices():
pi1.submit()
+ pi2 = make_purchase_invoice(company="_Test Company GST",
+ customer = '_Test Registered Supplier',
+ currency = 'INR',
+ item = 'Milk',
+ warehouse = 'Finished Goods - _GST',
+ expense_account = 'Cost of Goods Sold - _GST',
+ cost_center = 'Main - _GST',
+ rate=250,
+ qty=1,
+ do_not_save=1
+ )
+ pi2.submit()
+
def make_suppliers():
if not frappe.db.exists("Supplier", "_Test Registered Supplier"):
frappe.get_doc({
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
index 41c7b231469..41a0f1193bc 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
+++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
@@ -50,6 +50,7 @@ class TaxExemption80GCertificate(Document):
frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('PAN Number'),
get_link_to_form('Company', self.company)))
+ @frappe.whitelist()
def set_company_address(self):
address = get_company_address(self.company)
self.company_address = address.company_address
@@ -70,6 +71,7 @@ class TaxExemption80GCertificate(Document):
else:
self.title = self.donor_name
+ @frappe.whitelist()
def get_payments(self):
if not self.member:
frappe.throw(_('Please select a Member first.'))
diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json
index 86290cfe524..f4a3542a60e 100644
--- a/erpnext/regional/india/e_invoice/einv_validation.json
+++ b/erpnext/regional/india/e_invoice/einv_validation.json
@@ -919,7 +919,8 @@
"minLength": 1,
"maxLength": 15,
"pattern": "^([0-9A-Z/-]){1,15}$",
- "description": "Tranport Document Number"
+ "description": "Tranport Document Number",
+ "validationMsg": "Transport Receipt No is invalid"
},
"TransDocDt": {
"type": "string",
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 96f7f1b224f..8eccc3f5659 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -87,10 +87,10 @@ def get_doc_details(invoice):
invoice_date=invoice_date
))
-def get_party_details(address_name):
+def get_party_details(address_name, company_address=None, billing_address=None, shipping_address=None):
d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
- if (not d.gstin
+ if ((not d.gstin and not shipping_address)
or not d.city
or not d.pincode
or not d.address_title
@@ -108,8 +108,7 @@ def get_party_details(address_name):
# according to einvoice standard
pincode = 999999
- return frappe._dict(dict(
- gstin=d.gstin,
+ party_address_details = frappe._dict(dict(
legal_name=sanitize_for_json(d.address_title),
location=sanitize_for_json(d.city),
pincode=d.pincode,
@@ -117,6 +116,9 @@ def get_party_details(address_name):
address_line1=sanitize_for_json(d.address_line1),
address_line2=sanitize_for_json(d.address_line2)
))
+ if d.gstin:
+ party_address_details.gstin = d.gstin
+ return party_address_details
def get_gstin_details(gstin):
if not hasattr(frappe.local, 'gstin_cache'):
@@ -328,14 +330,17 @@ def make_einvoice(invoice):
item_list = get_item_list(invoice)
doc_details = get_doc_details(invoice)
invoice_value_details = get_invoice_value_details(invoice)
- seller_details = get_party_details(invoice.company_address)
+ seller_details = get_party_details(invoice.company_address, company_address=1)
if invoice.gst_category == 'Overseas':
buyer_details = get_overseas_address_details(invoice.customer_address)
else:
- buyer_details = get_party_details(invoice.customer_address)
- place_of_supply = get_place_of_supply(invoice, invoice.doctype) or sanitize_for_json(invoice.billing_address_gstin)
- place_of_supply = place_of_supply[:2]
+ buyer_details = get_party_details(invoice.customer_address, billing_address=1)
+ place_of_supply = get_place_of_supply(invoice, invoice.doctype)
+ if place_of_supply:
+ place_of_supply = place_of_supply.split('-')[0]
+ else:
+ place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
buyer_details.update(dict(place_of_supply=place_of_supply))
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
@@ -343,7 +348,7 @@ def make_einvoice(invoice):
if invoice.gst_category == 'Overseas':
shipping_details = get_overseas_address_details(invoice.shipping_address_name)
else:
- shipping_details = get_party_details(invoice.shipping_address_name)
+ shipping_details = get_party_details(invoice.shipping_address_name, shipping_address=1)
if invoice.is_pos and invoice.base_paid_amount:
payment_details = get_payment_details(invoice)
@@ -391,7 +396,9 @@ def safe_json_load(json_string):
snippet = json_string[start:end]
frappe.throw(_("Error in input data. Please check for any special characters near following input: {}").format(snippet))
-def validate_einvoice(validations, einvoice, errors=[]):
+def validate_einvoice(validations, einvoice, errors=None):
+ if errors is None:
+ errors = []
for fieldname, field_validation in validations.items():
value = einvoice.get(fieldname, None)
if not value or value == "None":
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index ee49aae0501..f7689cfa190 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe, os, json
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.permissions import add_permission, update_permission_property
from erpnext.regional.india import states
from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
@@ -18,6 +19,7 @@ def setup(company=None, patch=True):
# TODO: for all countries
def setup_company_independent_fixtures():
make_custom_fields()
+ make_property_setters()
add_permissions()
add_custom_roles_for_reports()
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
@@ -110,6 +112,11 @@ def add_print_formats():
frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
+def make_property_setters():
+ # GST rules do not allow for an invoice no. bigger than 16 characters
+ make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
+ make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
+
def make_custom_fields(update=True):
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
@@ -860,4 +867,4 @@ def create_gratuity_rule():
})
rule.flags.ignore_mandatory = True
- rule.save()
\ No newline at end of file
+ rule.save()
diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py
index f899349ccc0..616c2b853df 100644
--- a/erpnext/regional/report/gstr_2/gstr_2.py
+++ b/erpnext/regional/report/gstr_2/gstr_2.py
@@ -44,7 +44,7 @@ class Gstr2Report(Gstr1Report):
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
invoice_details = self.invoices.get(inv)
for rate, items in items_based_on_rate.items():
- if rate:
+ if rate or invoice_details.get('gst_category') == 'Registered Composition':
if inv not in self.igst_invoices:
rate = rate / 2
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
@@ -86,7 +86,7 @@ class Gstr2Report(Gstr1Report):
conditions += opts[1]
if self.filters.get("type_of_business") == "B2B":
- conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1 "
+ conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ', 'Registered Composition') and is_return != 1 "
elif self.filters.get("type_of_business") == "CDNR":
conditions += """ and is_return = 1 """
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index c4525946088..96b3fa4ccd5 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -230,13 +230,20 @@ class Customer(TransactionBase):
frappe.db.set(self, "customer_name", newdn)
def set_loyalty_program(self):
- if self.loyalty_program: return
+ if self.loyalty_program:
+ return
+
loyalty_program = get_loyalty_programs(self)
- if not loyalty_program: return
+ if not loyalty_program:
+ return
+
if len(loyalty_program) == 1:
self.loyalty_program = loyalty_program[0]
else:
- frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
+ frappe.msgprint(
+ _("Multiple Loyalty Programs found for Customer {}. Please select manually.")
+ .format(frappe.bold(self.customer_name))
+ )
def create_onboarding_docs(self, args):
defaults = frappe.defaults.get_defaults()
@@ -340,7 +347,6 @@ def _set_missing_values(source, target):
@frappe.whitelist()
def get_loyalty_programs(doc):
''' returns applicable loyalty programs for a customer '''
- from frappe.desk.treeview import get_children
lp_details = []
loyalty_programs = frappe.get_all("Loyalty Program",
@@ -349,15 +355,33 @@ def get_loyalty_programs(doc):
"ifnull(to_date, '2500-01-01')": [">=", today()]})
for loyalty_program in loyalty_programs:
- customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + [loyalty_program.customer_group]
- customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + [loyalty_program.customer_territory]
-
- if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
- and (not loyalty_program.customer_territory or doc.territory in customer_territories):
+ if (
+ (not loyalty_program.customer_group
+ or doc.customer_group in get_nested_links(
+ "Customer Group",
+ loyalty_program.customer_group,
+ doc.flags.ignore_permissions
+ ))
+ and (not loyalty_program.customer_territory
+ or doc.territory in get_nested_links(
+ "Territory",
+ loyalty_program.customer_territory,
+ doc.flags.ignore_permissions
+ ))
+ ):
lp_details.append(loyalty_program.name)
return lp_details
+def get_nested_links(link_doctype, link_name, ignore_permissions=False):
+ from frappe.desk.treeview import _get_children
+
+ links = [link_name]
+ for d in _get_children(link_doctype, link_name, ignore_permissions):
+ links.append(d.value)
+
+ return links
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
@@ -572,4 +596,4 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil
""", {
'customer': customer,
'txt': '%%%s%%' % txt
- })
\ No newline at end of file
+ })
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 5da248c1b52..246f9234a41 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -64,6 +64,7 @@ class Quotation(SellingController):
opp = frappe.get_doc("Opportunity", opportunity)
opp.set_status(status=status, update=True)
+ @frappe.whitelist()
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
if not self.has_sales_order():
get_lost_reasons = frappe.get_list('Quotation Lost Reason',
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index e56129170c1..d9e52e1d69d 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -150,7 +150,7 @@ class SalesOrder(SellingController):
if enq:
frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0]))
- def update_prevdoc_status(self, flag):
+ def update_prevdoc_status(self, flag=None):
for quotation in list(set([d.prevdoc_docname for d in self.get("items")])):
if quotation:
doc = frappe.get_doc("Quotation", quotation)
@@ -372,6 +372,7 @@ class SalesOrder(SellingController):
self.indicator_color = "green"
self.indicator_title = _("Paid")
+ @frappe.whitelist()
def get_work_order_items(self, for_raw_material_request=0):
'''Returns items with BOM that already do not have a linked work order'''
items = []
@@ -778,6 +779,7 @@ def get_events(start, end, filters=None):
@frappe.whitelist()
def make_purchase_order_for_default_supplier(source_name, selected_items=None, target_doc=None):
+ """Creates Purchase Order for each Supplier. Returns a list of doc objects."""
if not selected_items: return
if isinstance(selected_items, string_types):
@@ -820,15 +822,16 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty))
target.project = source_parent.project
- suppliers = [item.get('supplier') for item in selected_items if item.get('supplier') and item.get('supplier')]
- suppliers = list(set(suppliers))
+ suppliers = [item.get('supplier') for item in selected_items if item.get('supplier')]
+ suppliers = list(dict.fromkeys(suppliers)) # remove duplicates while preserving order
- items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')]
+ items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code')]
items_to_map = list(set(items_to_map))
if not suppliers:
frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order."))
+ purchase_orders = []
for supplier in suppliers:
doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {
@@ -872,7 +875,9 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
doc.insert()
frappe.db.commit()
- return doc
+ purchase_orders.append(doc)
+
+ return purchase_orders
@frappe.whitelist()
def make_purchase_order(source_name, selected_items=None, target_doc=None):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 0fdfb1b889e..3137621fd71 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -341,6 +341,9 @@ class TestSalesOrder(unittest.TestCase):
prev_total = so.get("base_total")
prev_total_in_words = so.get("base_in_words")
+ # get reserved qty before update items
+ reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
+
first_item_of_so = so.get("items")[0]
trans_item = json.dumps([
{'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \
@@ -354,6 +357,10 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(so.get("items")[-1].rate, 200)
self.assertEqual(so.get("items")[-1].qty, 7)
self.assertEqual(so.get("items")[-1].amount, 1400)
+
+ # reserved qty should increase after adding row
+ self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 7)
+
self.assertEqual(so.status, 'To Deliver and Bill')
updated_total = so.get("base_total")
@@ -373,6 +380,9 @@ class TestSalesOrder(unittest.TestCase):
create_dn_against_so(so.name, 2)
make_sales_invoice(so.name)
+ # get reserved qty before update items
+ reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
+
# add an item so as to try removing items
trans_item = json.dumps([
{"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name},
@@ -382,6 +392,9 @@ class TestSalesOrder(unittest.TestCase):
so.reload()
self.assertEqual(len(so.get("items")), 2)
+ # reserved qty should increase after adding row
+ self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 2)
+
# check if delivered items can be removed
trans_item = json.dumps([{
"item_code": '_Test Item 2',
@@ -402,6 +415,10 @@ class TestSalesOrder(unittest.TestCase):
so.reload()
self.assertEqual(len(so.get("items")), 1)
+
+ # reserved qty should decrease (back to initial) after deleting row
+ self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item)
+
self.assertEqual(so.status, 'To Deliver and Bill')
@@ -503,12 +520,18 @@ class TestSalesOrder(unittest.TestCase):
so = make_sales_order(item_code = "_Test Item", warehouse=None)
+ # get reserved qty of packed item
+ existing_reserved_qty = get_reserved_qty("_Packed Item")
+
added_item = json.dumps([{"item_code" : "_Product Bundle Item", "rate" : 200, 'qty' : 2}])
update_child_qty_rate('Sales Order', added_item, so.name)
so.reload()
self.assertEqual(so.packed_items[0].qty, 4)
+ # reserved qty in packed item should increase after adding bundle item
+ self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 4)
+
# test uom and conversion factor change
update_uom_conv_factor = json.dumps([{
'item_code': so.get("items")[0].item_code,
@@ -523,6 +546,9 @@ class TestSalesOrder(unittest.TestCase):
so.reload()
self.assertEqual(so.packed_items[0].qty, 8)
+ # reserved qty in packed item should increase after changing bundle item uom
+ self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 8)
+
def test_update_child_with_tax_template(self):
"""
Test Action: Create a SO with one item having its tax account head already in the SO.
@@ -736,7 +762,7 @@ class TestSalesOrder(unittest.TestCase):
so = make_sales_order(item_list=so_items, do_not_submit=True)
so.submit()
- po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+ po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
po.submit()
dn = create_dn_against_so(so.name, delivered_qty=2)
@@ -818,7 +844,7 @@ class TestSalesOrder(unittest.TestCase):
so.submit()
# create po for only one item
- po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+ po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
po1.submit()
self.assertEqual(so.customer, po1.customer)
@@ -828,7 +854,7 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(len(po1.items), 1)
# create po for remaining item
- po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])
+ po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])[0]
po2.submit()
# teardown
@@ -839,6 +865,45 @@ class TestSalesOrder(unittest.TestCase):
so.load_from_db()
so.cancel()
+ def test_drop_shipping_full_for_default_suppliers(self):
+ """Test if multiple POs are generated in one go against different default suppliers."""
+ from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier
+
+ if not frappe.db.exists("Item", "_Test Item for Drop Shipping 1"):
+ make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+ if not frappe.db.exists("Item", "_Test Item for Drop Shipping 2"):
+ make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+ so_items = [
+ {
+ "item_code": "_Test Item for Drop Shipping 1",
+ "warehouse": "",
+ "qty": 2,
+ "rate": 400,
+ "delivered_by_supplier": 1,
+ "supplier": '_Test Supplier'
+ },
+ {
+ "item_code": "_Test Item for Drop Shipping 2",
+ "warehouse": "",
+ "qty": 2,
+ "rate": 400,
+ "delivered_by_supplier": 1,
+ "supplier": '_Test Supplier 1'
+ }
+ ]
+
+ # create so and po
+ so = make_sales_order(item_list=so_items, do_not_submit=True)
+ so.submit()
+
+ purchase_orders = make_purchase_order_for_default_supplier(so.name, selected_items=so_items)
+
+ self.assertEqual(len(purchase_orders), 2)
+ self.assertEqual(purchase_orders[0].supplier, '_Test Supplier')
+ self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1')
+
def test_reserved_qty_for_closing_so(self):
bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
fields=["reserved_qty"])
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index c041d269a76..c2b5e4f9a90 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -259,6 +259,7 @@ erpnext.company.setup_queries = function(frm) {
["default_payroll_payable_account", {"root_type": "Liability"}],
["round_off_account", {"root_type": "Expense"}],
["write_off_account", {"root_type": "Expense"}],
+ ["default_discount_account", {}],
["discount_allowed_account", {"root_type": "Expense"}],
["discount_received_account", {"root_type": "Income"}],
["exchange_gain_loss_account", {"root_type": "Expense"}],
@@ -275,7 +276,7 @@ erpnext.company.setup_queries = function(frm) {
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
- ["unrealized_profit_loss_account", {"root_type": "Liability"}]
+ ["unrealized_profit_loss_account", {"root_type": "Liability"},]
], function(i, v) {
erpnext.company.set_custom_query(frm, v);
});
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 56f60dfcff0..83cbf475abd 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -59,6 +59,7 @@
"default_deferred_expense_account",
"default_payroll_payable_account",
"default_expense_claim_payable_account",
+ "default_discount_account",
"section_break_22",
"cost_center",
"column_break_26",
@@ -733,6 +734,12 @@
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
+ },
+ {
+ "fieldname": "default_discount_account",
+ "fieldtype": "Link",
+ "label": "Default Payment Discount Account",
+ "options": "Account"
}
],
"icon": "fa fa-building",
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 433851cde53..09221714d3d 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -66,6 +66,7 @@ class Company(NestedSet):
if frappe.db.sql("select abbr from tabCompany where name!=%s and abbr=%s", (self.name, self.abbr)):
frappe.throw(_("Abbreviation already used for another company"))
+ @frappe.whitelist()
def create_default_tax_template(self):
from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
create_sales_tax({
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index cbb4c7c5deb..ac55fdfdb83 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -24,6 +24,7 @@ class EmailDigest(Document):
self._accounts = {}
self.currency = frappe.db.get_value('Company', self.company, "default_currency")
+ @frappe.whitelist()
def get_users(self):
"""get list of users"""
user_list = frappe.db.sql("""
@@ -41,6 +42,7 @@ class EmailDigest(Document):
frappe.response['user_list'] = user_list
+ @frappe.whitelist()
def send(self):
# send email only to enabled users
valid_users = [p[0] for p in frappe.db.sql("""select name from `tabUser`
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py
index fa7bc504b68..76a84508291 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.py
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.py
@@ -50,6 +50,7 @@ class GlobalDefaults(Document):
# clear cache
frappe.clear_cache()
+ @frappe.whitelist()
def get_defaults(self):
return frappe.defaults.get_defaults()
diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py
index 2ea0bc08ca2..c4f1de14e41 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.py
+++ b/erpnext/setup/doctype/naming_series/naming_series.py
@@ -15,6 +15,7 @@ from frappe.core.doctype.doctype.doctype import validate_series
class NamingSeriesNotSetError(frappe.ValidationError): pass
class NamingSeries(Document):
+ @frappe.whitelist()
def get_transactions(self, arg=None):
doctypes = list(set(frappe.db.sql_list("""select parent
from `tabDocField` df where fieldname='naming_series'""")
@@ -53,6 +54,7 @@ class NamingSeries(Document):
options = list(filter(lambda x: x, [cstr(n).strip() for n in ol]))
return options
+ @frappe.whitelist()
def update_series(self, arg=None):
"""update series list"""
self.validate_series_set()
@@ -139,10 +141,12 @@ class NamingSeries(Document):
if not re.match("^[\w\- /.#{}]*$", n, re.UNICODE):
throw(_('Special Characters except "-", "#", ".", "/", "{" and "}" not allowed in naming series'))
+ @frappe.whitelist()
def get_options(self, arg=None):
if frappe.get_meta(arg or self.select_doc_for_series).get_field("naming_series"):
return frappe.get_meta(arg or self.select_doc_for_series).get_field("naming_series").options
+ @frappe.whitelist()
def get_current(self, arg=None):
"""get series current"""
if self.prefix:
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index 04d624ec0b7..8e79f0e5552 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "MAT-BIN-.YYYY.-.#####",
"creation": "2013-01-10 16:34:25",
"doctype": "DocType",
@@ -112,7 +113,8 @@
{
"fieldname": "reserved_qty_for_sub_contract",
"fieldtype": "Float",
- "label": "Reserved Qty for sub contract"
+ "label": "Reserved Qty for sub contract",
+ "read_only": 1
},
{
"fieldname": "ma_rate",
@@ -166,7 +168,8 @@
"hide_toolbar": 1,
"idx": 1,
"in_create": 1,
- "modified": "2019-11-18 18:34:59.456882",
+ "links": [],
+ "modified": "2021-03-30 23:09:39.572776",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
@@ -196,5 +199,6 @@
],
"quick_entry": 1,
"search_fields": "item_code,warehouse",
+ "sort_field": "modified",
"sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 35443906c86..d326a041730 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -101,7 +101,7 @@ class DeliveryNote(SellingController):
for f in fieldname:
toggle_print_hide(self.meta if key == "parent" else item_meta, f)
- super(DeliveryNote, self).before_print()
+ super(DeliveryNote, self).before_print(settings)
def set_actual_qty(self):
for d in self.get('items'):
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 28e9533186c..de85bc3922c 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -90,6 +90,7 @@ class DeliveryTrip(Document):
delivery_notes = [get_link_to_form("Delivery Note", note) for note in delivery_notes]
frappe.msgprint(_("Delivery Notes {0} updated").format(", ".join(delivery_notes)))
+ @frappe.whitelist()
def process_route(self, optimize):
"""
Estimate the arrival times for each stop in the Delivery Trip.
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 7b7d2da969c..7cb84a69f0b 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -50,6 +50,7 @@ class Item(WebsiteGenerator):
self.set_onload('stock_exists', self.stock_ledger_created())
self.set_asset_naming_series()
+ @frappe.whitelist()
def set_asset_naming_series(self):
if not hasattr(self, '_asset_naming_series'):
from erpnext.assets.doctype.asset.asset import get_asset_naming_series
@@ -706,6 +707,7 @@ class Item(WebsiteGenerator):
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
frappe.db.auto_commit_on_many_writes = 0
+ @frappe.whitelist()
def copy_specification_from_item_group(self):
self.set("website_specifications", [])
if self.item_group:
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index 909c4eeb906..c1f20a47b71 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -12,6 +12,7 @@
"item_name": "_Test Item",
"apply_warehouse_wise_reorder_level": 1,
"gst_hsn_code": "999800",
+ "opening_stock": 10,
"valuation_rate": 100,
"item_defaults": [{
"company": "_Test Company",
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 69a8bf19d34..83109469fc7 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -12,6 +12,7 @@ from erpnext.accounts.doctype.account.account import get_account_currency
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
class LandedCostVoucher(Document):
+ @frappe.whitelist()
def get_items_from_purchase_receipts(self):
self.set("items", [])
for pr in self.get("purchase_receipts"):
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 527b0d3ea93..7dfc5da50d6 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -354,6 +354,10 @@ frappe.ui.form.on('Material Request', {
},
material_request_type: function(frm) {
frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
+
+ if (frm.doc.material_request_type !== 'Material Transfer' && frm.doc.set_from_warehouse) {
+ frm.set_value('set_from_warehouse', '');
+ }
},
});
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index d73349dd39a..8d7b238c17f 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -20,9 +20,9 @@
"company",
"amended_from",
"warehouse_section",
- "set_warehouse",
- "column_break5",
"set_from_warehouse",
+ "column_break5",
+ "set_warehouse",
"items_section",
"scan_barcode",
"items",
@@ -314,7 +314,7 @@
"idx": 70,
"is_submittable": 1,
"links": [],
- "modified": "2020-09-19 01:04:09.285862",
+ "modified": "2021-03-31 23:52:55.392512",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index a7a29cca7f8..2008bffcd32 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -152,6 +152,7 @@ class PackingSlip(Document):
return cint(recommended_case_no[0][0]) + 1
+ @frappe.whitelist()
def get_items(self):
self.set("items", [])
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index d723fac11be..61b72092e70 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -33,6 +33,7 @@ class PickList(Document):
frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
.format(frappe.bold(item.item_code), frappe.bold(item.idx)), title=_("Quantity Mismatch"))
+ @frappe.whitelist()
def set_item_locations(self, save=False):
items = self.aggregate_item_qty()
self.item_location_map = frappe._dict()
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 70687bdac26..5d7597b2db5 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -176,7 +176,7 @@ class PurchaseReceipt(BuyingController):
if flt(self.per_billed) < 100:
self.update_billing_status()
else:
- self.status = "Completed"
+ self.db_set("status", "Completed")
# Updating stock ledger should always be called after updating prevdoc status,
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 7741ee7f609..7f0c3fa8010 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -191,7 +191,7 @@ class TestPurchaseReceipt(unittest.TestCase):
rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")])
self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
-
+
pr.cancel()
def test_subcontracting_gle_fg_item_rate_zero(self):
@@ -912,6 +912,57 @@ class TestPurchaseReceipt(unittest.TestCase):
ste1.cancel()
po.cancel()
+
+ def test_po_to_pi_and_po_to_pr_worflow_full(self):
+ """Test following behaviour:
+ - Create PO
+ - Create PI from PO and submit
+ - Create PR from PO and submit
+ """
+ from erpnext.buying.doctype.purchase_order import test_purchase_order
+ from erpnext.buying.doctype.purchase_order import purchase_order
+
+ po = test_purchase_order.create_purchase_order()
+
+ pi = purchase_order.make_purchase_invoice(po.name)
+ pi.submit()
+
+ pr = purchase_order.make_purchase_receipt(po.name)
+ pr.submit()
+
+ pr.load_from_db()
+
+ self.assertEqual(pr.status, "Completed")
+ self.assertEqual(pr.per_billed, 100)
+
+ def test_po_to_pi_and_po_to_pr_worflow_partial(self):
+ """Test following behaviour:
+ - Create PO
+ - Create partial PI from PO and submit
+ - Create PR from PO and submit
+ """
+ from erpnext.buying.doctype.purchase_order import test_purchase_order
+ from erpnext.buying.doctype.purchase_order import purchase_order
+
+ po = test_purchase_order.create_purchase_order()
+
+ pi = purchase_order.make_purchase_invoice(po.name)
+ pi.items[0].qty /= 2 # roughly 50%, ^ this function only creates PI with 1 item.
+ pi.submit()
+
+ pr = purchase_order.make_purchase_receipt(po.name)
+ pr.save()
+ # per_billed is only updated after submission.
+ self.assertEqual(flt(pr.per_billed), 0)
+
+ pr.submit()
+
+ pi.load_from_db()
+ pr.load_from_db()
+
+ self.assertEqual(pr.status, "To Bill")
+ self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
+
def get_sl_entries(voucher_type, voucher_no):
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 58b1eca2d33..469511af60b 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -18,6 +18,7 @@ class QualityInspection(Document):
if self.readings:
self.inspect_and_set_status()
+ @frappe.whitelist()
def get_item_specification_details(self):
if not self.quality_inspection_template:
self.quality_inspection_template = frappe.db.get_value('Item',
@@ -32,6 +33,7 @@ class QualityInspection(Document):
child.update(d)
child.status = "Accepted"
+ @frappe.whitelist()
def get_quality_inspection_template(self):
template = ''
if self.bom_no:
@@ -62,17 +64,21 @@ class QualityInspection(Document):
(quality_inspection, self.modified, self.reference_name, self.item_code))
else:
+ args = [quality_inspection, self.modified, self.reference_name, self.item_code]
doctype = self.reference_type + ' Item'
+
if self.reference_type == 'Stock Entry':
doctype = 'Stock Entry Detail'
if self.reference_type and self.reference_name:
conditions = ""
if self.batch_no and self.docstatus == 1:
- conditions += " and t1.batch_no = '%s'"%(self.batch_no)
+ conditions += " and t1.batch_no = %s"
+ args.append(self.batch_no)
if self.docstatus == 2: # if cancel, then remove qi link wherever same name
- conditions += " and t1.quality_inspection = '%s'"%(self.name)
+ conditions += " and t1.quality_inspection = %s"
+ args.append(self.name)
frappe.db.sql("""
UPDATE
@@ -85,7 +91,7 @@ class QualityInspection(Document):
and t1.parent = t2.name
{conditions}
""".format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions),
- (quality_inspection, self.modified, self.reference_name, self.item_code))
+ args)
def inspect_and_set_status(self):
for reading in self.readings:
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 559f9a5ed9e..f8cfdf83e76 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -39,6 +39,7 @@ class RepostItemValuation(Document):
frappe.enqueue(repost, timeout=1800, queue='long',
job_name='repost_sle', now=frappe.flags.in_test, doc=self)
+ @frappe.whitelist()
def restart_reposting(self):
self.set_status('Queued')
frappe.enqueue(repost, timeout=1800, queue='long',
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 64dcbed1d85..98246fb0240 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -100,6 +100,13 @@ frappe.ui.form.on('Stock Entry', {
frm.add_fetch("bom_no", "inspection_required", "inspection_required");
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+
+ frappe.db.get_single_value('Stock Settings', 'disable_serial_no_and_batch_selector')
+ .then((value) => {
+ if (value) {
+ frappe.flags.hide_serial_batch_dialog = true;
+ }
+ });
},
setup_quality_inspection: function(frm) {
@@ -721,7 +728,7 @@ frappe.ui.form.on('Stock Entry Detail', {
no_batch_serial_number_value = !d.batch_no;
}
- if (no_batch_serial_number_value) {
+ if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog) {
erpnext.stock.select_batch_and_serial_no(frm, d);
}
}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index b5f7e05f224..f8ac400a8e9 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -839,6 +839,7 @@ class StockEntry(StockController):
if not pro_doc.operations:
pro_doc.set_actual_dates()
+ @frappe.whitelist()
def get_item_details(self, args=None, for_update=False):
item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item,
@@ -913,6 +914,7 @@ class StockEntry(StockController):
return ret
+ @frappe.whitelist()
def set_items_for_stock_in(self):
self.items = []
@@ -937,6 +939,7 @@ class StockEntry(StockController):
'batch_no': d.batch_no
})
+ @frappe.whitelist()
def get_items(self):
self.set('items', [])
self.validate_work_order()
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 7ebd4e6cb20..349d8ae6794 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
@@ -313,8 +313,8 @@ class TestStockLedgerEntry(unittest.TestCase):
# Set User with Stock User role but not Stock Manager
try:
- frappe.set_user("test@example.com")
user = frappe.get_doc("User", "test@example.com")
+ frappe.set_user(user.name)
user.add_roles("Stock User")
user.remove_roles("Stock Manager")
@@ -325,7 +325,9 @@ class TestStockLedgerEntry(unittest.TestCase):
# Block back-dated entry
self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit)
+ frappe.set_user("Administrator")
user.add_roles("Stock Manager")
+ frappe.set_user(user.name)
# Back dated entry allowed to Stock Manager
back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
@@ -337,6 +339,7 @@ class TestStockLedgerEntry(unittest.TestCase):
finally:
frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None)
frappe.set_user("Administrator")
+ user.remove_roles("Stock Manager")
def create_repack_entry(**args):
@@ -400,4 +403,4 @@ def create_items():
make_item(d, properties=properties)
- return items
\ No newline at end of file
+ return items
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 70e4c2c40e0..e23f7d43d9d 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -110,7 +110,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
get_gross_profit(out)
if args.doctype == 'Material Request':
out.rate = args.rate or out.price_list_rate
- out.amount = flt(args.qty * out.rate)
+ out.amount = flt(args.qty) * flt(out.rate)
return out
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index bbbbc4a5270..767a8a6a296 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -165,6 +165,7 @@ class Issue(Document):
communication.ignore_mandatory = True
communication.save()
+ @frappe.whitelist()
def split_issue(self, subject, communication_id):
# Bug: Pressing enter doesn't send subject
from copy import deepcopy
@@ -259,6 +260,7 @@ class Issue(Document):
self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement))
+ @frappe.whitelist()
def reset_service_level_agreement(self, reason, user):
if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
|