Merge pull request #25944 from ruchamahabal/fix-employee-onboarding-status
fix: boarding status in Employee Onboarding and Separation
diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml
index 5a5098b..8b21979 100644
--- a/.github/helper/semgrep_rules/security.yml
+++ b/.github/helper/semgrep_rules/security.yml
@@ -8,18 +8,3 @@
dynamic content. Avoid it or use safe_eval().
languages: [python]
severity: ERROR
-
-- id: frappe-sqli-format-strings
- patterns:
- - pattern-inside: |
- @frappe.whitelist()
- def $FUNC(...):
- ...
- - pattern-either:
- - pattern: frappe.db.sql("..." % ...)
- - pattern: frappe.db.sql(f"...", ...)
- - pattern: frappe.db.sql("...".format(...), ...)
- message: |
- Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
- languages: [python]
- severity: WARNING
diff --git a/.github/stale.yml b/.github/stale.yml
index dabc66eb..9322ae8 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -1,11 +1,11 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
-daysUntilStale: 30
+daysUntilStale: 15
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
-daysUntilClose: 7
+daysUntilClose: 3
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 7c6b843..1d180f2 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -1,16 +1,25 @@
name: Backport
on:
- pull_request:
+ pull_request_target:
types:
- closed
- labeled
jobs:
- backport:
- runs-on: ubuntu-18.04
- name: Backport
+ main:
+ runs-on: ubuntu-latest
steps:
- - name: Backport
- uses: tibdex/backport@v1
+ - name: Checkout Actions
+ uses: actions/checkout@v2
with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+ repository: "frappe/backport"
+ path: ./actions
+ ref: develop
+ - name: Install Actions
+ run: npm install --production --prefix ./actions
+ - name: Run backport
+ uses: ./actions/backport
+ with:
+ token: ${{secrets.BACKPORT_BOT_TOKEN}}
+ labelsToAdd: "backport"
+ title: "{{originalTitle}}"
diff --git a/CODEOWNERS b/CODEOWNERS
index 219b6bb..a4a14de 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -21,13 +21,13 @@
erpnext/shopping_cart/ @marination
erpnext/stock/ @marination @rohitwaghchaure @ankush
-erpnext/crm/ @ruchamahabal
-erpnext/education/ @ruchamahabal
-erpnext/healthcare/ @ruchamahabal
-erpnext/hr/ @ruchamahabal
+erpnext/crm/ @ruchamahabal @pateljannat
+erpnext/education/ @ruchamahabal @pateljannat
+erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
+erpnext/hr/ @ruchamahabal @pateljannat
erpnext/non_profit/ @ruchamahabal
-erpnext/payroll @ruchamahabal
-erpnext/projects/ @ruchamahabal
+erpnext/payroll @ruchamahabal @pateljannat
+erpnext/projects/ @ruchamahabal @pateljannat
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 1166549..a181c2d 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.7.0'
+__version__ = '13.7.1'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 5619321..f2b0a8c 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -27,6 +27,9 @@
if not (self.company and self.posting_date):
frappe.throw(_("Please select Company and Posting Date to getting entries"))
+ def on_cancel(self):
+ self.ignore_linked_doctypes = ('GL Entry')
+
@frappe.whitelist()
def check_journal_entry_condition(self):
total_debit = frappe.db.get_value("Journal Entry Account", {
@@ -99,10 +102,12 @@
sum(debit) - sum(credit) as balance
from `tabGL Entry`
where account in (%s)
- group by account, party_type, party
+ and posting_date <= %s
+ and is_cancelled = 0
+ group by account, NULLIF(party_type,''), NULLIF(party,'')
having sum(debit) != sum(credit)
order by account
- """ % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
+ """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
return account_details
@@ -143,9 +148,9 @@
"party_type": d.get("party_type"),
"party": d.get("party"),
"account_currency": d.get("account_currency"),
- "balance": d.get("balance_in_account_currency"),
- dr_or_cr: abs(d.get("balance_in_account_currency")),
- "exchange_rate":d.get("new_exchange_rate"),
+ "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
+ dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
+ "exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
})
@@ -154,9 +159,9 @@
"party_type": d.get("party_type"),
"party": d.get("party"),
"account_currency": d.get("account_currency"),
- "balance": d.get("balance_in_account_currency"),
- reverse_dr_or_cr: abs(d.get("balance_in_account_currency")),
- "exchange_rate": d.get("current_exchange_rate"),
+ "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
+ reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
+ "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name
})
@@ -185,9 +190,9 @@
account_details = {}
company_currency = erpnext.get_company_currency(company)
- balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False)
+ balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
if balance:
- balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party)
+ balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index e7dd6b8..0a9a105 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -48,6 +48,8 @@
"shipping_address",
"company_address",
"company_address_display",
+ "dispatch_address_name",
+ "dispatch_address",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -1966,6 +1968,21 @@
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "dispatch_address_name",
+ "fieldtype": "Link",
+ "label": "Dispatch Address Name",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "dispatch_address",
+ "fieldtype": "Small Text",
+ "label": "Dispatch Address",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
@@ -1978,7 +1995,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2021-05-20 22:48:33.988881",
+ "modified": "2021-07-08 14:03:55.502522",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 6dc2767..c6e6e3d 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1939,6 +1939,8 @@
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
+ self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
+ self.assertEqual(data['billLists'][0]['fromStateCode'],27)
def test_einvoice_submission_without_irn(self):
# init
@@ -2092,6 +2094,30 @@
address.save()
+ if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'):
+ address = frappe.get_doc({
+ "address_line1": "_Test Dispatch Address Line 1",
+ "address_title": "_Test Dispatch-Address for Eway bill",
+ "address_type": "Shipping",
+ "city": "_Test City",
+ "state": "Test State",
+ "country": "India",
+ "doctype": "Address",
+ "is_primary_address": 0,
+ "phone": "+910000000000",
+ "gstin": "07AAACC1206D1ZI",
+ "gst_state": "Delhi",
+ "gst_state_number": "07",
+ "pincode": "1100101"
+ }).insert()
+
+ address.append("links", {
+ "link_doctype": "Company",
+ "link_name": "_Test Company"
+ })
+
+ address.save()
+
def make_test_transporter_for_ewaybill():
if not frappe.db.exists('Supplier', '_Test Transporter'):
frappe.get_doc({
@@ -2130,6 +2156,7 @@
si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing"
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
+ si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping"
si.vehicle_no = "KA12KA1234"
si.gst_category = "Registered Regular"
si.mode_of_transport = 'Road'
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
index 1b7a0fe..cfdb167 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
@@ -27,7 +27,8 @@
"base_tax_amount",
"base_total",
"base_tax_amount_after_discount_amount",
- "item_wise_tax_detail"
+ "item_wise_tax_detail",
+ "dont_recompute_tax"
],
"fields": [
{
@@ -200,13 +201,22 @@
"fieldname": "included_in_paid_amount",
"fieldtype": "Check",
"label": "Considered In Paid Amount"
+ },
+ {
+ "default": "0",
+ "fieldname": "dont_recompute_tax",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Dont Recompute tax",
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-06-14 01:44:36.899147",
+ "modified": "2021-07-27 12:40:59.051803",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges",
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index a11b77a..b54646f 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -99,7 +99,6 @@
voucher_no = gle.voucher_no,
party = gle.party,
posting_date = gle.posting_date,
- remarks = gle.remarks,
account_currency = gle.account_currency,
invoiced = 0.0,
paid = 0.0,
@@ -579,7 +578,7 @@
self.gl_entries = frappe.db.sql("""
select
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
- against_voucher_type, against_voucher, account_currency, remarks, {0}
+ against_voucher_type, against_voucher, account_currency, {0}
from
`tabGL Entry`
where
@@ -792,8 +791,6 @@
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
options='Supplier Group')
- self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200)
-
def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
if not fieldname: fieldname = scrub(label)
if fieldtype=='Currency': options='currency'
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 4c313c4..cdd865a 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1112,8 +1112,11 @@
for d in self.get("payment_schedule"):
if d.invoice_portion:
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
- d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
+ d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
d.outstanding = d.payment_amount
+ elif not d.invoice_portion:
+ d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
+
def set_due_date(self):
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 2526e6d..17bd735 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -53,12 +53,17 @@
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for d in self.get("items"):
if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
- serial_nos = get_serial_nos(d.serial_no)
- for serial_no_data in frappe.get_all("Serial No",
- filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]):
- if serial_no_data.batch_no != d.batch_no:
+ serial_nos = frappe.get_all("Serial No",
+ fields=["batch_no", "name", "warehouse"],
+ filters={
+ "name": ("in", get_serial_nos(d.serial_no))
+ }
+ )
+
+ for row in serial_nos:
+ if row.warehouse and row.batch_no != d.batch_no:
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
- .format(d.idx, serial_no_data.name, d.batch_no))
+ .format(d.idx, row.name, d.batch_no))
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 56da5b7..099c7d4 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -152,7 +152,7 @@
validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, self.doc)
- if not self.doc.get('is_consolidated'):
+ if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
tax.item_wise_tax_detail = {}
tax_fields = ["total", "tax_amount_after_discount_amount",
@@ -347,7 +347,7 @@
elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.qty
- if not self.doc.get("is_consolidated"):
+ if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
return current_tax_amount
@@ -455,7 +455,8 @@
def _cleanup(self):
if not self.doc.get('is_consolidated'):
for tax in self.doc.get("taxes"):
- tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
+ if not tax.get("dont_recompute_tax"):
+ tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
def set_discount_amount(self):
if self.doc.additional_discount_percentage:
diff --git a/erpnext/crm/doctype/campaign/__init__.py b/erpnext/crm/doctype/campaign/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/campaign/__init__.py
diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js
new file mode 100644
index 0000000..11bfa74
--- /dev/null
+++ b/erpnext/crm/doctype/campaign/campaign.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Campaign', {
+ refresh: function(frm) {
+ erpnext.toggle_naming_series();
+
+ if (frm.doc.__islocal) {
+ frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
+ } else {
+ cur_frm.add_custom_button(__("View Leads"), function() {
+ frappe.route_options = {"source": "Campaign", "campaign_name": frm.doc.name};
+ frappe.set_route("List", "Lead");
+ }, "fa fa-list", true);
+ }
+ }
+});
diff --git a/erpnext/selling/doctype/campaign/campaign.json b/erpnext/crm/doctype/campaign/campaign.json
similarity index 95%
rename from erpnext/selling/doctype/campaign/campaign.json
rename to erpnext/crm/doctype/campaign/campaign.json
index 986ac13..f833f4c 100644
--- a/erpnext/selling/doctype/campaign/campaign.json
+++ b/erpnext/crm/doctype/campaign/campaign.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
@@ -39,17 +40,9 @@
"set_only_once": 1
},
{
- "fieldname": "description",
- "fieldtype": "Text",
- "in_list_view": 1,
- "label": "Description",
- "oldfieldname": "description",
- "oldfieldtype": "Text",
- "width": "300px"
- },
- {
- "fieldname": "description_section",
- "fieldtype": "Section Break"
+ "fieldname": "campaign_schedules_section",
+ "fieldtype": "Section Break",
+ "label": "Campaign Schedules"
},
{
"fieldname": "campaign_schedules",
@@ -58,16 +51,25 @@
"options": "Campaign Email Schedule"
},
{
- "fieldname": "campaign_schedules_section",
- "fieldtype": "Section Break",
- "label": "Campaign Schedules"
+ "fieldname": "description_section",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Text",
+ "width": "300px"
}
],
"icon": "fa fa-bullhorn",
"idx": 1,
- "modified": "2019-07-22 12:03:39.832342",
+ "links": [],
+ "modified": "2021-06-30 18:05:06.412712",
"modified_by": "Administrator",
- "module": "Selling",
+ "module": "CRM",
"name": "Campaign",
"owner": "Administrator",
"permissions": [
diff --git a/erpnext/selling/doctype/campaign/campaign.py b/erpnext/crm/doctype/campaign/campaign.py
similarity index 66%
rename from erpnext/selling/doctype/campaign/campaign.py
rename to erpnext/crm/doctype/campaign/campaign.py
index 1094542..e32799f 100644
--- a/erpnext/selling/doctype/campaign/campaign.py
+++ b/erpnext/crm/doctype/campaign/campaign.py
@@ -1,9 +1,7 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
-from __future__ import unicode_literals
import frappe
-
from frappe.model.document import Document
from frappe.model.naming import set_name_by_naming_series
diff --git a/erpnext/crm/doctype/campaign/test_campaign.py b/erpnext/crm/doctype/campaign/test_campaign.py
new file mode 100644
index 0000000..7124b8c
--- /dev/null
+++ b/erpnext/crm/doctype/campaign/test_campaign.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestCampaign(unittest.TestCase):
+ pass
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 9717bb9..59b011d 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -25,7 +25,8 @@
"Address": "public/js/address.js",
"Communication": "public/js/communication.js",
"Event": "public/js/event.js",
- "Newsletter": "public/js/newsletter.js"
+ "Newsletter": "public/js/newsletter.js",
+ "Contact": "public/js/contact.js"
}
override_doctype_class = {
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index c566688..3f50b41 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -83,7 +83,7 @@
if (!frm.doc.__islocal && frm.doc.docstatus<2) {
frm.add_custom_button(__("Update Cost"), function() {
- frm.events.update_cost(frm);
+ frm.events.update_cost(frm, true);
});
frm.add_custom_button(__("Browse BOM"), function() {
frappe.route_options = {
@@ -318,14 +318,15 @@
})
},
- update_cost: function(frm) {
+ update_cost: function(frm, save_doc=false) {
return frappe.call({
doc: frm.doc,
method: "update_cost",
freeze: true,
args: {
update_parent: true,
- from_child_bom:false
+ save: save_doc,
+ from_child_bom: false
},
callback: function(r) {
refresh_field("items");
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 9da461f..c68198b 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -330,7 +330,7 @@
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
if not from_child_bom:
- frappe.msgprint(_("Cost Updated"))
+ frappe.msgprint(_("Cost Updated"), alert=True)
def update_parent_cost(self):
if self.total_cost:
@@ -774,7 +774,7 @@
item.image,
bom.project,
bom_item.rate,
- bom_item.amount,
+ sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount,
item.stock_uom,
item.item_group,
item.allow_alternative_item,
@@ -1069,13 +1069,6 @@
if barcodes:
or_cond_filters["name"] = ("in", barcodes)
- for cond in get_match_cond(doctype, as_condition=False):
- for key, value in cond.items():
- if key == doctype:
- key = "name"
-
- query_filters[key] = ("in", value)
-
if filters and filters.get("item_code"):
has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
if not has_variants:
@@ -1084,7 +1077,7 @@
if filters and filters.get("is_stock_item"):
query_filters["is_stock_item"] = 1
- return frappe.get_all("Item",
+ return frappe.get_list("Item",
fields = fields, filters=query_filters,
or_filters = or_cond_filters, order_by=order_by,
limit_start=start, limit_page_length=page_len, as_list=1)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 420bb00..69c7f5c 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -192,11 +192,11 @@
"completed_qty": args.get("completed_qty") or 0.0
})
elif args.get("start_time"):
- new_args = {
+ new_args = frappe._dict({
"from_time": get_datetime(args.get("start_time")),
"operation": args.get("sub_operation"),
"completed_qty": 0.0
- }
+ })
if employees:
for name in employees:
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 38a0ee7..6a024f2 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -747,9 +747,8 @@
group by item_code, warehouse
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
-def get_warehouse_list(warehouses, warehouse_list=None):
- if not warehouse_list:
- warehouse_list = []
+def get_warehouse_list(warehouses):
+ warehouse_list = []
if isinstance(warehouses, str):
warehouses = json.loads(warehouses)
@@ -761,23 +760,19 @@
else:
warehouse_list.append(row.get("warehouse"))
+ return warehouse_list
+
@frappe.whitelist()
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
- warehouse_list = []
if warehouses:
- get_warehouse_list(warehouses, warehouse_list)
-
- if warehouse_list:
- warehouses = list(set(warehouse_list))
+ warehouses = list(set(get_warehouse_list(warehouses)))
if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses:
warehouses.remove(doc.get("for_warehouse"))
- warehouse_list = None
-
doc['mr_items'] = []
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index cce1bb6..93e6d7a 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -10,7 +10,7 @@
from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
-from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
+from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list
class TestProductionPlan(unittest.TestCase):
def setUp(self):
@@ -251,6 +251,27 @@
pln.cancel()
frappe.delete_doc("Production Plan", pln.name)
+ def test_get_warehouse_list_group(self):
+ """Check if required warehouses are returned"""
+ warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
+
+ warehouses = set(get_warehouse_list(warehouse_json))
+ expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"}
+
+ missing_warehouse = expected_warehouses - warehouses
+
+ self.assertTrue(len(missing_warehouse) == 0,
+ msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
+
+ def test_get_warehouse_list_single(self):
+ warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
+
+ warehouses = set(get_warehouse_list(warehouse_json))
+ expected_warehouses = {"_Test Scrap Warehouse - _TC", }
+
+ self.assertEqual(warehouses, expected_warehouses)
+
+
def create_production_plan(**args):
args = frappe._dict(args)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 0a8e532..282b5d0 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -487,21 +487,20 @@
return
operations = []
- if not self.use_multi_level_bom:
- bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
- operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
- else:
+
+ if self.use_multi_level_bom:
bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
- bom_traversal = list(reversed(bom_tree.level_order_traversal()))
- bom_traversal.append(bom_tree) # add operation on top level item last
+ bom_traversal = reversed(bom_tree.level_order_traversal())
- for d in bom_traversal:
- if d.is_bom:
- operations.extend(_get_operations(d.name, qty=d.exploded_qty))
+ for node in bom_traversal:
+ if node.is_bom:
+ operations.extend(_get_operations(node.name, qty=node.exploded_qty))
- for correct_index, operation in enumerate(operations, start=1):
- operation.idx = correct_index
+ bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
+ operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
+ for correct_index, operation in enumerate(operations, start=1):
+ operation.idx = correct_index
self.set('operations', operations)
self.calculate_time()
@@ -656,7 +655,7 @@
for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999):
self.append('required_items', {
'rate': item.rate,
- 'amount': item.amount,
+ 'amount': item.rate * item.qty,
'operation': item.operation or operation,
'item_code': item.item_code,
'item_name': item.item_name,
diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json
index f190cfa..7c1baf1 100644
--- a/erpnext/non_profit/doctype/member/member.json
+++ b/erpnext/non_profit/doctype/member/member.json
@@ -26,7 +26,7 @@
"razorpay_details_section",
"subscription_id",
"customer_id",
- "subscription_activated",
+ "subscription_status",
"column_break_21",
"subscription_start",
"subscription_end"
@@ -152,12 +152,6 @@
"fieldtype": "Column Break"
},
{
- "default": "0",
- "fieldname": "subscription_activated",
- "fieldtype": "Check",
- "label": "Subscription Activated"
- },
- {
"fieldname": "subscription_start",
"fieldtype": "Date",
"label": "Subscription Start "
@@ -166,11 +160,17 @@
"fieldname": "subscription_end",
"fieldtype": "Date",
"label": "Subscription End"
+ },
+ {
+ "fieldname": "subscription_status",
+ "fieldtype": "Select",
+ "label": "Subscription Status",
+ "options": "\nActive\nHalted"
}
],
"image_field": "image",
"links": [],
- "modified": "2020-11-09 12:12:10.174647",
+ "modified": "2021-07-11 14:27:26.368039",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Member",
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 30be585..67828d6 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -84,7 +84,9 @@
"email_id": user_details.email,
"pan_number": user_details.pan or None,
"membership_type": user_details.plan_id,
- "subscription_id": user_details.subscription_id or None
+ "customer_id": user_details.customer_id or None,
+ "subscription_id": user_details.subscription_id or None,
+ "subscription_status": user_details.subscription_status or ""
})
member.insert(ignore_permissions=True)
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index e8ae618..b584116 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -196,11 +196,14 @@
return invoice
-def get_member_based_on_subscription(subscription_id, email):
- members = frappe.get_all("Member", filters={
- "subscription_id": subscription_id,
- "email_id": email
- }, order_by="creation desc")
+def get_member_based_on_subscription(subscription_id, email=None, customer_id=None):
+ filters = {"subscription_id": subscription_id}
+ if email:
+ filters.update({"email_id": email})
+ if customer_id:
+ filters.update({"customer_id": customer_id})
+
+ members = frappe.get_all("Member", filters=filters, order_by="creation desc")
try:
return frappe.get_doc("Member", members[0]["name"])
@@ -209,8 +212,6 @@
def verify_signature(data, endpoint="Membership"):
- if frappe.flags.in_test or os.environ.get("CI"):
- return True
signature = frappe.request.headers.get("X-Razorpay-Signature")
settings = frappe.get_doc("Non Profit Settings")
@@ -225,16 +226,7 @@
@frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data(as_text=True)
- try:
- verify_signature(data)
- except Exception as e:
- log = frappe.log_error(e, "Membership Webhook Verification Error")
- notify_failure(log)
- return { "status": "Failed", "reason": e}
-
- if isinstance(data, six.string_types):
- data = json.loads(data)
- data = frappe._dict(data)
+ data = process_request_data(data)
subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription)
@@ -281,7 +273,7 @@
# Update membership values
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
- member.subscription_activated = 1
+ member.subscription_status = "Active"
member.flags.ignore_mandatory = True
member.save()
@@ -294,9 +286,67 @@
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id)
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
notify_failure(log)
- return { "status": "Failed", "reason": e}
+ return {"status": "Failed", "reason": e}
- return { "status": "Success" }
+ return {"status": "Success"}
+
+
+@frappe.whitelist(allow_guest=True)
+def update_halted_razorpay_subscription(*args, **kwargs):
+ """
+ When all retries have been exhausted, Razorpay moves the subscription to the halted state.
+ The customer has to manually retry the charge or change the card linked to the subscription,
+ for the subscription to move back to the active state.
+ """
+ if frappe.request:
+ data = frappe.request.get_data(as_text=True)
+ data = process_request_data(data)
+ elif frappe.flags.in_test:
+ data = kwargs.get("data")
+ data = frappe._dict(data)
+ else:
+ return
+
+ if not data.event == "subscription.halted":
+ return
+
+ subscription = data.payload.get("subscription", {}).get("entity", {})
+ subscription = frappe._dict(subscription)
+
+ try:
+ member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id)
+ if not member:
+ frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id))
+
+ member.subscription_status = "Halted"
+ member.flags.ignore_mandatory = True
+ member.save()
+
+ if subscription.get("notes"):
+ member = get_additional_notes(member, subscription)
+
+ except Exception as e:
+ message = "{0}\n\n{1}".format(e, frappe.get_traceback())
+ log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name))
+ notify_failure(log)
+ return {"status": "Failed", "reason": e}
+
+ return {"status": "Success"}
+
+
+def process_request_data(data):
+ try:
+ verify_signature(data)
+ except Exception as e:
+ log = frappe.log_error(e, "Membership Webhook Verification Error")
+ notify_failure(log)
+ return {"status": "Failed", "reason": e}
+
+ if isinstance(data, six.string_types):
+ data = json.loads(data)
+ data = frappe._dict(data)
+
+ return data
def get_company_for_memberships():
@@ -362,4 +412,4 @@
`tabMembership` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled') AND `to_date` < %s
- """, (nowdate()))
\ No newline at end of file
+ """, (nowdate()))
diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py
index 31da792..0f5a9be 100644
--- a/erpnext/non_profit/doctype/membership/test_membership.py
+++ b/erpnext/non_profit/doctype/membership/test_membership.py
@@ -6,6 +6,7 @@
import frappe
import erpnext
from erpnext.non_profit.doctype.member.member import create_member
+from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription
from frappe.utils import nowdate, add_months
class TestMembership(unittest.TestCase):
@@ -13,11 +14,16 @@
plan = setup_membership()
# make test member
- self.member_doc = create_member(frappe._dict({
- 'fullname': "_Test_Member",
- 'email': "_test_member_erpnext@example.com",
- 'plan_id': plan.name
- }))
+ self.member_doc = create_member(
+ frappe._dict({
+ "fullname": "_Test_Member",
+ "email": "_test_member_erpnext@example.com",
+ "plan_id": plan.name,
+ "subscription_id": "sub_DEX6xcJ1HSW4CR",
+ "customer_id": "cust_C0WlbKhp3aLA7W",
+ "subscription_status": "Active"
+ })
+ )
self.member_doc.make_customer_and_link()
self.member = self.member_doc.name
@@ -51,6 +57,20 @@
"to_date": add_months(nowdate(), 3),
})
+ def test_halted_memberships(self):
+ make_membership(self.member, {
+ "from_date": add_months(nowdate(), 2),
+ "to_date": add_months(nowdate(), 3)
+ })
+
+ self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active")
+ payload = get_subscription_payload()
+ update_halted_razorpay_subscription(data=payload)
+ self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted")
+
+ def tearDown(self):
+ frappe.db.rollback()
+
def set_config(key, value):
frappe.db.set_value("Non Profit Settings", None, key, value)
@@ -115,4 +135,28 @@
else:
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
- return plan
\ No newline at end of file
+ return plan
+
+def get_subscription_payload():
+ return {
+ "entity": "event",
+ "account_id": "acc_BFQ7uQEaa7j2z7",
+ "event": "subscription.halted",
+ "contains": [
+ "subscription"
+ ],
+ "payload": {
+ "subscription": {
+ "entity": {
+ "id": "sub_DEX6xcJ1HSW4CR",
+ "entity": "subscription",
+ "plan_id": "_rzpy_test_milythm",
+ "customer_id": "cust_C0WlbKhp3aLA7W",
+ "status": "halted",
+ "notes": {
+ "Important": "Notes for Internal Reference"
+ },
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0ae8130..a029627a 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -295,3 +295,5 @@
erpnext.patches.v13_0.update_job_card_details
erpnext.patches.v13_0.update_level_in_bom #1234sswef
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
+erpnext.patches.v13_0.update_subscription_status_in_memberships
+erpnext.patches.v13_0.update_amt_in_work_order_required_items
diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
index 48999e6..d7ad1fc 100644
--- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
+++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
@@ -10,6 +10,7 @@
if not frappe.db.has_column('Work Order', 'has_batch_no'):
return
+ frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings')
if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
return
@@ -107,4 +108,4 @@
"company": doc.company
})
- create_repost_item_valuation_entry(args)
\ No newline at end of file
+ create_repost_item_valuation_entry(args)
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index fa1dfed..41c51c3 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -37,7 +37,7 @@
if frappe.db.exists('DocType', 'Opportunity'):
opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc')
- frappe.reload_doc('crm', 'doctype', 'opportunity')
+ frappe.reload_doctype('Opportunity', force=True)
rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
# change fieldtype to duration
diff --git a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
new file mode 100644
index 0000000..eae5ff6
--- /dev/null
+++ b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
@@ -0,0 +1,10 @@
+import frappe
+
+def execute():
+ """ Correct amount in child table of required items table."""
+
+ frappe.reload_doc("manufacturing", "doctype", "work_order")
+ frappe.reload_doc("manufacturing", "doctype", "work_order_item")
+
+ frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
+
diff --git a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py
new file mode 100644
index 0000000..28e650e
--- /dev/null
+++ b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py
@@ -0,0 +1,9 @@
+import frappe
+
+def execute():
+ if frappe.db.exists('DocType', 'Member'):
+ frappe.reload_doc('Non Profit', 'doctype', 'Member')
+
+ if frappe.db.has_column('Member', 'subscription_activated'):
+ frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1')
+ frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated')
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js
index dbf7514..e9e6f81 100644
--- a/erpnext/payroll/doctype/salary_component/salary_component.js
+++ b/erpnext/payroll/doctype/salary_component/salary_component.js
@@ -4,11 +4,18 @@
frappe.ui.form.on('Salary Component', {
setup: function(frm) {
frm.set_query("account", "accounts", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
+ let d = frappe.get_doc(cdt, cdn);
+
+ let root_type = "Liability";
+ if (frm.doc.type == "Deduction") {
+ root_type = "Expense";
+ }
+
return {
filters: {
"is_group": 0,
- "company": d.company
+ "company": d.company,
+ "root_type": root_type
}
};
});
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 211b94a..d60b1a2 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -101,7 +101,7 @@
return html
def set_item_group_filters(field_filters):
- if 'item_group' in field_filters:
+ if field_filters is not None and 'item_group' in field_filters:
field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index 142fe79..239fbb9 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -16,7 +16,7 @@
doctype: "Bank Transaction",
filters: { name: this.bank_transaction_name },
fieldname: [
- "date",
+ "date as reference_date",
"deposit",
"withdrawal",
"currency",
diff --git a/erpnext/public/js/contact.js b/erpnext/public/js/contact.js
new file mode 100644
index 0000000..41a0e8a
--- /dev/null
+++ b/erpnext/public/js/contact.js
@@ -0,0 +1,16 @@
+
+
+frappe.ui.form.on("Contact", {
+ refresh(frm) {
+ frm.set_query('link_doctype', "links", function() {
+ return {
+ query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes",
+ filters: {
+ fieldtype: ["in", ["HTML", "Text Editor"]],
+ fieldname: ["in", ["contact_html", "company_description"]],
+ }
+ };
+ });
+ frm.refresh_field("links");
+ }
+});
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 181e340..a495a9b 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -65,7 +65,7 @@
this.frm.refresh_fields();
}
- calculate_discount_amount(){
+ calculate_discount_amount() {
if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) {
this.calculate_item_values();
this.calculate_net_total();
@@ -75,18 +75,15 @@
}
_calculate_taxes_and_totals() {
- frappe.run_serially([
- () => this.validate_conversion_rate(),
- () => this.calculate_item_values(),
- () => this.update_item_tax_map(),
- () => this.initialize_taxes(),
- () => this.determine_exclusive_rate(),
- () => this.calculate_net_total(),
- () => this.calculate_taxes(),
- () => this.manipulate_grand_total_for_inclusive_tax(),
- () => this.calculate_totals(),
- () => this._cleanup()
- ]);
+ this.validate_conversion_rate();
+ this.calculate_item_values();
+ this.initialize_taxes();
+ this.determine_exclusive_rate();
+ this.calculate_net_total();
+ this.calculate_taxes();
+ this.manipulate_grand_total_for_inclusive_tax();
+ this.calculate_totals();
+ this._cleanup();
}
validate_conversion_rate() {
@@ -270,46 +267,6 @@
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
}
- update_item_tax_map() {
- let me = this;
- let item_codes = [];
- let item_rates = {};
- let item_tax_templates = {};
-
- $.each(this.frm.doc.items || [], function(i, item) {
- if (item.item_code) {
- // Use combination of name and item code in case same item is added multiple times
- item_codes.push([item.item_code, item.name]);
- item_rates[item.name] = item.net_rate;
- item_tax_templates[item.name] = item.item_tax_template;
- }
- });
-
- if (item_codes.length) {
- return this.frm.call({
- method: "erpnext.stock.get_item_details.get_item_tax_info",
- args: {
- company: me.frm.doc.company,
- tax_category: cstr(me.frm.doc.tax_category),
- item_codes: item_codes,
- item_rates: item_rates,
- item_tax_templates: item_tax_templates
- },
- callback: function(r) {
- if (!r.exc) {
- $.each(me.frm.doc.items || [], function(i, item) {
- if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
- item.item_tax_template = r.message[item.name].item_tax_template;
- item.item_tax_rate = r.message[item.name].item_tax_rate;
- me.add_taxes_from_item_tax_template(item.item_tax_rate);
- }
- });
- }
- }
- });
- }
- }
-
add_taxes_from_item_tax_template(item_tax_map) {
let me = this;
@@ -634,8 +591,6 @@
tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail);
});
}
-
- this.frm.refresh_fields();
}
set_discount_amount() {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 8360337..33366db 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -846,9 +846,9 @@
frappe.run_serially([
() => me.frm.script_manager.trigger("currency"),
+ () => me.update_item_tax_map(),
() => me.apply_default_taxes(),
- () => me.apply_pricing_rule(),
- () => me.calculate_taxes_and_totals()
+ () => me.apply_pricing_rule()
]);
}
}
@@ -1807,6 +1807,46 @@
]);
}
+ update_item_tax_map() {
+ let me = this;
+ let item_codes = [];
+ let item_rates = {};
+ let item_tax_templates = {};
+
+ $.each(this.frm.doc.items || [], function(i, item) {
+ if (item.item_code) {
+ // Use combination of name and item code in case same item is added multiple times
+ item_codes.push([item.item_code, item.name]);
+ item_rates[item.name] = item.net_rate;
+ item_tax_templates[item.name] = item.item_tax_template;
+ }
+ });
+
+ if (item_codes.length) {
+ return this.frm.call({
+ method: "erpnext.stock.get_item_details.get_item_tax_info",
+ args: {
+ company: me.frm.doc.company,
+ tax_category: cstr(me.frm.doc.tax_category),
+ item_codes: item_codes,
+ item_rates: item_rates,
+ item_tax_templates: item_tax_templates
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ $.each(me.frm.doc.items || [], function(i, item) {
+ if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
+ item.item_tax_template = r.message[item.name].item_tax_template;
+ item.item_tax_rate = r.message[item.name].item_tax_rate;
+ me.add_taxes_from_item_tax_template(item.item_tax_rate);
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+
item_tax_template(doc, cdt, cdn) {
var me = this;
if(me.frm.updating_party_details) return;
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
index cc2d9f0..54e4886 100644
--- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
@@ -3,7 +3,7 @@
frappe.ui.form.on('E Invoice Settings', {
refresh(frm) {
- const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
+ const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing';
frm.dashboard.set_headline(
__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
);
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 6415204..ea39fe1 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -214,9 +214,8 @@
for d in item_details:
if d.item_code not in self.invoice_items.get(d.parent, {}):
- self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
- sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details
- if i.item_code == d.item_code and i.parent == d.parent))
+ self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
+ self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
self.is_nil_exempt.append(d.item_code)
@@ -322,6 +321,9 @@
inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value
inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100)
+ if self.invoice_cess.get(inv):
+ self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2)
+
self.set_inter_state_supply(inter_state_supply_details)
def set_supplies_liable_to_reverse_charge(self):
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 81c0918..fbe47d0 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -431,9 +431,11 @@
company_address = frappe.get_doc('Address', doc.company_address)
billing_address = frappe.get_doc('Address', doc.customer_address)
+ #added dispatch address
+ dispatch_address = frappe.get_doc('Address', doc.dispatch_address_name) if doc.dispatch_address_name else company_address
shipping_address = frappe.get_doc('Address', doc.shipping_address_name)
- data = get_address_details(data, doc, company_address, billing_address)
+ data = get_address_details(data, doc, company_address, billing_address, dispatch_address)
data.itemList = []
data.totalValue = doc.total
@@ -519,10 +521,10 @@
`tabDynamic Link`.link_name = %(company)s""", {"company": company})
return company_gstins
-def get_address_details(data, doc, company_address, billing_address):
+def get_address_details(data, doc, company_address, billing_address, dispatch_address):
data.fromPincode = validate_pincode(company_address.pincode, 'Company Address')
- data.fromStateCode = data.actualFromStateCode = validate_state_code(
- company_address.gst_state_number, 'Company Address')
+ data.fromStateCode = validate_state_code(company_address.gst_state_number, 'Company Address')
+ data.actualFromStateCode = validate_state_code(dispatch_address.gst_state_number, 'Dispatch Address')
if not doc.billing_address_gstin or len(doc.billing_address_gstin) < 15:
data.toGstin = 'URP'
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index cfcb8c3..b81fa81 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -217,9 +217,8 @@
for d in items:
if d.item_code not in self.invoice_items.get(d.parent, {}):
- self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
- sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items
- if i.item_code == d.item_code and i.parent == d.parent))
+ self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
+ self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
item_tax_rate = {}
diff --git a/erpnext/selling/doctype/campaign/README.md b/erpnext/selling/doctype/campaign/README.md
deleted file mode 100644
index a837318..0000000
--- a/erpnext/selling/doctype/campaign/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Sales campaign / promotion, like special discount, exhibition, newsletter etc.
\ No newline at end of file
diff --git a/erpnext/selling/doctype/campaign/__init__.py b/erpnext/selling/doctype/campaign/__init__.py
deleted file mode 100644
index baffc48..0000000
--- a/erpnext/selling/doctype/campaign/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from __future__ import unicode_literals
diff --git a/erpnext/selling/doctype/campaign/campaign.js b/erpnext/selling/doctype/campaign/campaign.js
deleted file mode 100644
index 72a90d0..0000000
--- a/erpnext/selling/doctype/campaign/campaign.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.ui.form.on("Campaign", "refresh", function(frm) {
- erpnext.toggle_naming_series();
- if(frm.doc.__islocal) {
- frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
- }
- else{
- cur_frm.add_custom_button(__("View Leads"), function() {
- frappe.route_options = {"source": "Campaign","campaign_name": frm.doc.name}
- frappe.set_route("List", "Lead");
- }, "fa fa-list", true);
- }
-})
diff --git a/erpnext/selling/doctype/campaign/campaign_dashboard.py b/erpnext/selling/doctype/campaign/campaign_dashboard.py
deleted file mode 100644
index 3cef560..0000000
--- a/erpnext/selling/doctype/campaign/campaign_dashboard.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from __future__ import unicode_literals
-from frappe import _
-
-def get_data():
- return {
- 'fieldname': 'campaign_name',
- 'transactions': [
- {
- 'label': _('Email Campaigns'),
- 'items': ['Email Campaign']
- },
- {
- 'label': _('Social Media Campaigns'),
- 'items': ['Social Media Post']
- }
- ]
- }
diff --git a/erpnext/selling/doctype/campaign/test_campaign.py b/erpnext/selling/doctype/campaign/test_campaign.py
deleted file mode 100644
index 4d062ff..0000000
--- a/erpnext/selling/doctype/campaign/test_campaign.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-
-
-import frappe
-test_records = frappe.get_test_records('Campaign')
\ No newline at end of file
diff --git a/erpnext/selling/doctype/campaign/test_records.json b/erpnext/selling/doctype/campaign/test_records.json
deleted file mode 100644
index 625d3b3..0000000
--- a/erpnext/selling/doctype/campaign/test_records.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "campaign_name": "_Test Campaign",
- "doctype": "Campaign"
- },
- {
- "campaign_name": "_Test Campaign 1",
- "doctype": "Campaign"
- }
-]
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 762b6f1..d31db82 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -38,6 +38,8 @@
"col_break46",
"shipping_address_name",
"shipping_address",
+ "dispatch_address_name",
+ "dispatch_address",
"customer_group",
"territory",
"currency_and_price_list",
@@ -1486,13 +1488,29 @@
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "dispatch_address_name",
+ "fieldtype": "Link",
+ "label": "Dispatch Address Name",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "depends_on": "dispatch_address_name",
+ "fieldname": "dispatch_address",
+ "fieldtype": "Small Text",
+ "label": "Dispatch Address",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2021-04-15 23:55:13.439068",
+ "modified": "2021-07-08 21:37:44.177493",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.css b/erpnext/selling/page/sales_funnel/sales_funnel.css
index 89e904f..455d37c 100644
--- a/erpnext/selling/page/sales_funnel/sales_funnel.css
+++ b/erpnext/selling/page/sales_funnel/sales_funnel.css
@@ -1,3 +1,4 @@
.funnel-wrapper {
margin: 15px;
+ width: 100%;
}
\ No newline at end of file
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index eb02867..f515baf 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -26,7 +26,7 @@
}
};
});
- }
+ }
setup_queries() {
var me = this;
@@ -85,7 +85,7 @@
refresh() {
super.refresh();
-
+
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
this.frm.toggle_display("customer_name",
@@ -114,6 +114,10 @@
erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name");
}
+ dispatch_address_name() {
+ erpnext.utils.get_address_display(this.frm, "dispatch_address_name", "dispatch_address");
+ }
+
sales_partner() {
this.apply_pricing_rule();
}
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index f20e76f..dbfeb4a 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -32,6 +32,8 @@
"contact_info",
"shipping_address_name",
"shipping_address",
+ "dispatch_address_name",
+ "dispatch_address",
"contact_person",
"contact_display",
"contact_mobile",
@@ -1282,13 +1284,28 @@
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
+ },
+ {
+ "fieldname": "dispatch_address_name",
+ "fieldtype": "Link",
+ "label": "Dispatch Address Name",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "dispatch_address_name",
+ "fieldname": "dispatch_address",
+ "fieldtype": "Small Text",
+ "label": "Dispatch Address",
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2021-06-11 19:27:30.901112",
+ "modified": "2021-07-08 21:37:20.802652",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 3ad9909..026b85e 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -162,8 +162,15 @@
from `tabStock Entry Detail` where material_request = %s
and material_request_item = %s and docstatus = 1""",
(self.name, d.name))[0][0])
+ mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance')
- if d.ordered_qty and d.ordered_qty > d.stock_qty:
+ if mr_qty_allowance:
+ allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100))
+ if d.ordered_qty and d.ordered_qty > allowed_qty:
+ frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
+ cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code))
+
+ elif d.ordered_qty and d.ordered_qty > d.stock_qty:
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 72a3a5e..b4776ba 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -329,6 +329,58 @@
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
+ def test_over_transfer_qty_allowance(self):
+ mr = frappe.new_doc('Material Request')
+ mr.company = "_Test Company"
+ mr.scheduled_date = today()
+ mr.append('items',{
+ "item_code": "_Test FG Item",
+ "item_name": "_Test FG Item",
+ "qty": 10,
+ "schedule_date": today(),
+ "uom": "_Test UOM 1",
+ "warehouse": "_Test Warehouse - _TC"
+ })
+
+ mr.material_request_type = "Material Transfer"
+ mr.insert()
+ mr.submit()
+
+ frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20)
+
+ # map a stock entry
+
+ se_doc = make_stock_entry(mr.name)
+ se_doc.update({
+ "posting_date": today(),
+ "posting_time": "00:00",
+ })
+ se_doc.get("items")[0].update({
+ "qty": 13,
+ "transfer_qty": 12.0,
+ "s_warehouse": "_Test Warehouse - _TC",
+ "t_warehouse": "_Test Warehouse 1 - _TC",
+ "basic_rate": 1.0
+ })
+
+ # make available the qty in _Test Warehouse 1 before transfer
+ sr = frappe.new_doc("Stock Reconciliation")
+ sr.company = "_Test Company"
+ sr.purpose = "Opening Stock"
+ sr.append('items', {
+ "item_code": "_Test FG Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 20,
+ "valuation_rate": 0.01
+ })
+ sr.insert()
+ sr.submit()
+ se = frappe.copy_doc(se_doc)
+ se.insert()
+ self.assertRaises(frappe.ValidationError)
+ se.items[0].qty = 12
+ se.submit()
+
def test_completed_qty_for_over_transfer(self):
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 2a9dcfb..f75cb56 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -18,6 +18,7 @@
"section_break_9",
"over_delivery_receipt_allowance",
"role_allowed_to_over_deliver_receive",
+ "mr_qty_allowance",
"column_break_12",
"auto_insert_price_list_rate_if_missing",
"allow_negative_stock",
@@ -283,6 +284,12 @@
"fieldtype": "Select",
"label": "Action If Quality Inspection Is Rejected",
"options": "Stop\nWarn"
+ },
+ {
+ "description": "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units.",
+ "fieldname": "mr_qty_allowance",
+ "fieldtype": "Float",
+ "label": "Over Transfer Allowance"
}
],
"icon": "icon-cog",
@@ -290,7 +297,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-07-10 16:17:42.159829",
+ "modified": "2021-06-28 17:02:26.683002",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
@@ -310,4 +317,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 4657700..cf52803 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -74,9 +74,8 @@
update_party_blanket_order(args, out)
- if not doc or cint(doc.get('is_return')) == 0:
- # get price list rate only if the invoice is not a credit or debit note
- get_price_list_rate(args, item, out)
+
+ get_price_list_rate(args, item, out)
if args.customer and cint(args.is_pos):
out.update(get_pos_profile_item_details(args.company, args, update_data=True))