Merge pull request #24971 from deepeshgarg007/loan_security_shortfall_process_update
fix: Pending shortfall update on processing loan security shortfall
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..399b176
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,32 @@
+[flake8]
+ignore =
+ E121,
+ E126,
+ E127,
+ E128,
+ E203,
+ E225,
+ E226,
+ E231,
+ E241,
+ E251,
+ E261,
+ E265,
+ E302,
+ E303,
+ E305,
+ E402,
+ E501,
+ E741,
+ W291,
+ W292,
+ W293,
+ W391,
+ W503,
+ W504,
+ F403,
+ B007,
+ B950,
+ W191,
+
+max-line-length = 200
\ No newline at end of file
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 253ad70..7b0f944 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -12,7 +12,7 @@
pip install frappe-bench
-git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF}" --depth 1
+git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site
@@ -43,4 +43,4 @@
bench get-app erpnext "${GITHUB_WORKSPACE}"
bench start &
-bench --site test_site reinstall --yes
\ No newline at end of file
+bench --site test_site reinstall --yes
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 2a1db14..78c2f5a 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -1,12 +1,10 @@
name: CI
-on:
- pull_request:
- workflow_dispatch:
+on: [pull_request, workflow_dispatch, push]
jobs:
test:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-18.04
strategy:
fail-fast: false
diff --git a/README.md b/README.md
index 15782a2..bb592ae 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
<p>ERP made simple</p>
</p>
-[![Build Status](https://api.travis-ci.com/frappe/erpnext.svg?branch=develop)](https://travis-ci.com/frappe/erpnext)
+[![CI](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml)
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
index 022d7a7..10cd939 100644
--- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
@@ -11,36 +11,36 @@
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
class TestAccountingPeriod(unittest.TestCase):
- def test_overlap(self):
- ap1 = create_accounting_period(start_date = "2018-04-01",
- end_date = "2018-06-30", company = "Wind Power LLC")
- ap1.save()
+ def test_overlap(self):
+ ap1 = create_accounting_period(start_date = "2018-04-01",
+ end_date = "2018-06-30", company = "Wind Power LLC")
+ ap1.save()
- ap2 = create_accounting_period(start_date = "2018-06-30",
- end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
- self.assertRaises(OverlapError, ap2.save)
+ ap2 = create_accounting_period(start_date = "2018-06-30",
+ end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
+ self.assertRaises(OverlapError, ap2.save)
- def test_accounting_period(self):
- ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
- ap1.save()
+ def test_accounting_period(self):
+ ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
+ ap1.save()
- doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC")
- self.assertRaises(ClosedAccountingPeriod, doc.submit)
+ doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
+ self.assertRaises(ClosedAccountingPeriod, doc.submit)
- def tearDown(self):
- for d in frappe.get_all("Accounting Period"):
- frappe.delete_doc("Accounting Period", d.name)
+ def tearDown(self):
+ for d in frappe.get_all("Accounting Period"):
+ frappe.delete_doc("Accounting Period", d.name)
def create_accounting_period(**args):
- args = frappe._dict(args)
+ args = frappe._dict(args)
- accounting_period = frappe.new_doc("Accounting Period")
- accounting_period.start_date = args.start_date or nowdate()
- accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
- accounting_period.company = args.company or "_Test Company"
- accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
- accounting_period.append("closed_documents", {
- "document_type": 'Sales Invoice', "closed": 1
- })
+ accounting_period = frappe.new_doc("Accounting Period")
+ accounting_period.start_date = args.start_date or nowdate()
+ accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
+ accounting_period.company = args.company or "_Test Company"
+ accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
+ accounting_period.append("closed_documents", {
+ "document_type": 'Sales Invoice', "closed": 1
+ })
- return accounting_period
\ No newline at end of file
+ return accounting_period
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index bdfe532..8d6de2d 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -6,10 +6,12 @@
import frappe
import unittest
-test_dependencies = ["Customer", "Supplier"]
+from frappe.cache_manager import clear_doctype_cache
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
+test_dependencies = ["Customer", "Supplier"]
+
class TestOpeningInvoiceCreationTool(unittest.TestCase):
def setUp(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
@@ -24,22 +26,25 @@
def test_opening_sales_invoice_creation(self):
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
- invoices = self.make_invoices(company="_Test Opening Invoice Company")
+ try:
+ invoices = self.make_invoices(company="_Test Opening Invoice Company")
- self.assertEqual(len(invoices), 2)
- expected_value = {
- "keys": ["customer", "outstanding_amount", "status"],
- 0: ["_Test Customer", 300, "Overdue"],
- 1: ["_Test Customer 1", 250, "Overdue"],
- }
- self.check_expected_values(invoices, expected_value)
+ self.assertEqual(len(invoices), 2)
+ expected_value = {
+ "keys": ["customer", "outstanding_amount", "status"],
+ 0: ["_Test Customer", 300, "Overdue"],
+ 1: ["_Test Customer 1", 250, "Overdue"],
+ }
+ self.check_expected_values(invoices, expected_value)
- si = frappe.get_doc("Sales Invoice", invoices[0])
+ si = frappe.get_doc("Sales Invoice", invoices[0])
- # Check if update stock is not enabled
- self.assertEqual(si.update_stock, 0)
+ # Check if update stock is not enabled
+ self.assertEqual(si.update_stock, 0)
- property_setter.delete()
+ finally:
+ property_setter.delete()
+ clear_doctype_cache("Sales Invoice")
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
@@ -143,4 +148,4 @@
customer.insert(ignore_permissions=True)
return customer.name
else:
- return frappe.db.exists("Customer", customer_name)
\ No newline at end of file
+ return frappe.db.exists("Customer", customer_name)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 6412772..b5f6a40 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -605,12 +605,22 @@
{fieldtype:"Column Break"},
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
{fieldtype:"Section Break"},
+ {fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
+ "get_query": function() {
+ return {
+ "filters": {"company": frm.doc.company}
+ }
+ }
+ },
+ {fieldtype:"Column Break"},
+ {fieldtype:"Section Break"},
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
];
frappe.prompt(fields, function(filters){
frappe.flags.allocate_payment_amount = true;
frm.events.validate_filters_data(frm, filters);
+ frm.doc.cost_center = filters.cost_center;
frm.events.get_outstanding_documents(frm, filters);
}, __("Filters"), __("Get Outstanding Documents"));
},
@@ -1066,11 +1076,6 @@
frm.set_value("paid_from_account_balance", r.message.paid_from_account_balance);
frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
frm.set_value("party_balance", r.message.party_balance);
- },
- () => {
- if(frm.doc.payment_type != "Internal") {
- frm.clear_table("references");
- }
}
]);
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index eb52fd6..054afe5 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -9,8 +9,16 @@
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.stock.doctype.item.test_item import make_item
class TestPOSInvoice(unittest.TestCase):
+ def tearDown(self):
+ if frappe.session.user != "Administrator":
+ frappe.set_user("Administrator")
+
+ if frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
+ frappe.db.set_value("Selling Settings", None, "validate_selling_price", 0)
+
def test_timestamp_change(self):
w = create_pos_invoice(do_not_save=1)
w.docstatus = 0
@@ -370,7 +378,6 @@
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 3470)
- frappe.set_user("Administrator")
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
@@ -412,7 +419,6 @@
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 840)
- frappe.set_user("Administrator")
def test_merging_with_validate_selling_price(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
@@ -421,10 +427,12 @@
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
- make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
+ item = "Test Selling Price Validation"
+ make_item(item, {"is_stock_item": 1})
+ make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300)
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
- pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+ pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
pos_inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
})
@@ -438,7 +446,7 @@
})
self.assertRaises(frappe.ValidationError, pos_inv.submit)
- pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
+ pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1)
pos_inv2.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
})
@@ -457,8 +465,6 @@
pos_inv2.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 400)
- frappe.set_user("Administrator")
- frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
def create_pos_invoice(**args):
args = frappe._dict(args)
@@ -508,4 +514,4 @@
else:
pos_inv.payment_schedule = []
- return pos_inv
\ No newline at end of file
+ return pos_inv
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 06aa20b..66a8e20 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -524,7 +524,7 @@
},
onload: function(frm) {
- if(frm.doc.__onload) {
+ if(frm.doc.__onload && frm.is_new()) {
if(frm.doc.supplier) {
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
}
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 4076be7..a1bf66b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -76,7 +76,7 @@
if not self.is_pos:
self.so_dn_required()
-
+
self.set_tax_withholding()
self.validate_proj_cust()
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 1b95578..90e2144 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1800,6 +1800,15 @@
si.selling_price_list = "_Test Price List Rest of the World"
si.update_stock = 1
si.items[0].target_warehouse = 'Work In Progress - TCP1'
+
+ # Add stock to stores for succesful stock transfer
+ make_stock_entry(
+ target="Stores - TCP1",
+ company = "_Test Company with perpetual inventory",
+ qty=1,
+ basic_rate=100
+ )
+
add_taxes(si)
si.save()
@@ -2269,4 +2278,4 @@
"cost_center": "Main - TCP1",
"description": "Excise Duty",
"rate": 12
- })
\ No newline at end of file
+ })
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
index 429a9f3..52d19d5 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
@@ -46,5 +46,5 @@
frappe.throw(_("Disabled template must not be default template"))
def validate_for_tax_category(doc):
- if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
+ if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index 9ce8e3f..dd3b49a 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -177,7 +177,7 @@
for d in purchase_invoices:
frappe.get_doc('Purchase Invoice', d).cancel()
-
+
for d in sales_invoices:
frappe.get_doc('Sales Invoice', d).cancel()
@@ -229,7 +229,8 @@
'qty': args.qty or 1,
'rate': args.rate or 10000,
'cost_center': 'Main - _TC',
- 'expense_account': 'Cost of Goods Sold - _TC'
+ 'expense_account': 'Cost of Goods Sold - _TC',
+ 'warehouse': args.warehouse or '_Test Warehouse - _TC'
}]
})
@@ -353,4 +354,4 @@
'company': '_Test Company',
'account': 'TDS - _TC'
}]
- }).insert()
\ No newline at end of file
+ }).insert()
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 7dfce85..14efa1f 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -51,7 +51,11 @@
"from_date": start_date
})
- to_date = add_months(start_date, months_to_add)
+ if i==0 and filter_based_on == 'Date Range':
+ to_date = add_months(get_first_day(start_date), months_to_add)
+ else:
+ to_date = add_months(start_date, months_to_add)
+
start_date = to_date
# Subtract one day from to_date, as it may be first day in next fiscal year or month
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 02d4865..604c886 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -723,7 +723,7 @@
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
make_stock_entry(target="_Test Warehouse - _TC",
- item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
+ item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 12a81c7..e8f27c3 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -829,10 +829,10 @@
party_account_currency = get_party_account_currency(party_type, party, self.company)
if (party_account_currency
- and party_account_currency != self.company_currency
- and self.currency != party_account_currency):
+ and (self.currency != party_account_currency
+ and self.currency != self.company_currency)):
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
- .format(party_type, party, party_account_currency), InvalidCurrency)
+ .format(party_type, party, frappe.bold(party_account_currency), InvalidCurrency))
# Note: not validating with gle account because we don't have the account
# at quotation / sales order level and we shouldn't stop someone
@@ -898,7 +898,7 @@
date = self.get("due_date")
due_date = date or posting_date
- if party_account_currency == self.company_currency:
+ if self.company_currency == self.currency:
grand_total = self.get("base_rounded_total") or self.base_grand_total
else:
grand_total = self.get("rounded_total") or self.grand_total
@@ -959,7 +959,7 @@
for d in self.get("payment_schedule"):
total += flt(d.payment_amount)
- if party_account_currency == self.company_currency:
+ if self.company_currency == self.currency:
total = flt(total, self.precision("base_grand_total"))
grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
else:
@@ -1394,7 +1394,7 @@
)
def get_new_child_item(item_row):
- child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
+ child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
def validate_quantity(child_item, d):
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index fb52c1f..edc40c4 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -144,7 +144,7 @@
if sales_person.commission_rate:
sales_person.incentives = flt(
- sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
+ sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
self.precision("incentives", sales_person))
total += sales_person.allocated_percentage
@@ -502,4 +502,4 @@
for d in obj.get("items"):
if d.item_code:
if getattr(d, "income_account", None):
- set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
\ No newline at end of file
+ set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index aab5770..c33e556 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -795,7 +795,7 @@
for d in self.doc.get(self.tax_field):
if d.account_currency == company_currency:
d.exchange_rate = 1
- elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date:
+ elif not d.exchange_rate:
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
account_currency=d.account_currency, company=self.doc.company)
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
index 74ad456..5f471ab 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
@@ -5,7 +5,7 @@
import frappe
import unittest, os, json
-from frappe.utils import cstr
+from frappe.utils import cstr, cint
from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
@@ -13,9 +13,14 @@
class ShopifySettings(unittest.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
frappe.set_user("Administrator")
+ cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock'))
+ if not cls.allow_negative_stock:
+ frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
+
# use the fixture data
import_doc(frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
@@ -24,9 +29,15 @@
frappe.reload_doctype("Delivery Note")
frappe.reload_doctype("Sales Invoice")
- self.setup_shopify()
+ cls.setup_shopify()
- def setup_shopify(self):
+ @classmethod
+ def tearDownClass(cls):
+ if not cls.allow_negative_stock:
+ frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
+
+ @classmethod
+ def setup_shopify(cls):
shopify_settings = frappe.get_doc("Shopify Settings")
shopify_settings.taxes = []
@@ -56,21 +67,20 @@
"delivery_note_series": "DN-"
}).save(ignore_permissions=True)
- self.shopify_settings = shopify_settings
+ cls.shopify_settings = shopify_settings
def test_order(self):
- ### Create Customer ###
+ # Create Customer
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
shopify_customer = json.load(shopify_customer)
create_customer(shopify_customer.get("customer"), self.shopify_settings)
- ### Create Item ###
+ # Create Item
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
shopify_item = json.load(shopify_item)
make_item("_Test Warehouse - _TC", shopify_item.get("product"))
-
- ### Create Order ###
+ # Create Order
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
shopify_order = json.load(shopify_order)
@@ -80,17 +90,17 @@
self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
- #check for customer
+ # Check for customer
shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
- #check sales invoice
+ # Check sales invoice
sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
- #check delivery note
+ # Check delivery note
delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
index 5ced845..aaf0e85 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -53,7 +53,7 @@
"discharge_ordered_date",
"discharge_practitioner",
"discharge_encounter",
- "discharge_date",
+ "discharge_datetime",
"cb_discharge",
"discharge_instructions",
"followup_date",
@@ -404,14 +404,15 @@
"permlevel": 1
},
{
- "fieldname": "discharge_date",
- "fieldtype": "Date",
+ "fieldname": "discharge_datetime",
+ "fieldtype": "Datetime",
"label": "Discharge Date",
"read_only": 1
}
],
+ "index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-05-21 02:26:22.144575",
+ "modified": "2021-03-18 14:44:11.689956",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Inpatient Record",
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index 88d7f0b..2934316 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -151,7 +151,7 @@
def discharge_patient(inpatient_record):
validate_inpatient_invoicing(inpatient_record)
- inpatient_record.discharge_date = today()
+ inpatient_record.discharge_datetime = now_datetime()
inpatient_record.status = "Discharged"
inpatient_record.save(ignore_permissions = True)
diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
index 92b1eae..3c42bd9 100644
--- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
@@ -8,6 +8,8 @@
from frappe.utils import nowdate
from datetime import date
+test_dependencies = ["Employee"]
+
class TestAttendanceRequest(unittest.TestCase):
def setUp(self):
for doctype in ["Attendance Request", "Attendance"]:
@@ -56,4 +58,4 @@
self.assertEqual(attendance.docstatus, 2)
def get_employee():
- return frappe.get_doc("Employee", "_T-Employee-00001")
\ No newline at end of file
+ return frappe.get_doc("Employee", "_T-Employee-00001")
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
index 1615ab3..74ce301 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
@@ -10,6 +10,8 @@
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
+test_dependencies = ["Employee"]
+
class TestCompensatoryLeaveRequest(unittest.TestCase):
def setUp(self):
frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
@@ -129,4 +131,4 @@
],
"holiday_list_name": "_Test Compensatory Leave"
})
- holiday_list.save()
\ No newline at end of file
+ holiday_list.save()
diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
index 6d275c8..8728342 100644
--- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
@@ -13,11 +13,21 @@
def create_job_applicant(**args):
args = frappe._dict(args)
- job_applicant = frappe.get_doc({
- "doctype": "Job Applicant",
+
+ filters = {
"applicant_name": args.applicant_name or "_Test Applicant",
"email_id": args.email_id or "test_applicant@example.com",
+ }
+
+ if frappe.db.exists("Job Applicant", filters):
+ return frappe.get_doc("Job Applicant", filters)
+
+ job_applicant = frappe.get_doc({
+ "doctype": "Job Applicant",
"status": args.status or "Open"
})
+
+ job_applicant.update(filters)
job_applicant.save()
- return job_applicant
\ No newline at end of file
+
+ return job_applicant
diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py
index 8886596..690a692 100644
--- a/erpnext/hr/doctype/job_offer/test_job_offer.py
+++ b/erpnext/hr/doctype/job_offer/test_job_offer.py
@@ -13,14 +13,15 @@
class TestJobOffer(unittest.TestCase):
def test_job_offer_creation_against_vacancies(self):
- create_staffing_plan(staffing_details=[{
- "designation": "Designer",
+ frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
+ job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
+ job_offer = create_job_offer(job_applicant=job_applicant.name, designation="UX Designer")
+
+ create_staffing_plan(name='Test No Vacancies', staffing_details=[{
+ "designation": "UX Designer",
"vacancies": 0,
"estimated_cost_per_position": 5000
}])
- frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
- job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
- job_offer = create_job_offer(job_applicant=job_applicant.name, designation="Researcher")
self.assertRaises(frappe.ValidationError, job_offer.submit)
# test creation of job offer when vacancies are not present
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 26f077a..0b71036 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -6,6 +6,10 @@
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation
class TestLeaveAllocation(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ frappe.db.sql("delete from `tabLeave Period`")
+
def test_overlapping_allocation(self):
frappe.db.sql("delete from `tabLeave Allocation`")
@@ -177,4 +181,4 @@
})
return leave_allocation
-test_dependencies = ["Employee", "Leave Type"]
\ No newline at end of file
+test_dependencies = ["Employee", "Leave Type"]
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 53b7a39..b335c48 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -12,7 +12,7 @@
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
-test_dependencies = ["Leave Allocation", "Leave Block List"]
+test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
_test_records = [
{
@@ -639,4 +639,4 @@
"docstatus": 1
}).insert()
- allocate_leave.submit()
\ No newline at end of file
+ allocate_leave.submit()
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index c7bc6fb..838e794 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -9,6 +9,8 @@
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
+test_dependencies = ["Employee"]
+
class TestLeavePolicyAssignment(unittest.TestCase):
def setUp(self):
diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py
index 3dcfcbf..230bb2b 100644
--- a/erpnext/hr/doctype/shift_request/test_shift_request.py
+++ b/erpnext/hr/doctype/shift_request/test_shift_request.py
@@ -7,6 +7,8 @@
import unittest
from frappe.utils import nowdate, add_days
+test_dependencies = ["Shift Type"]
+
class TestShiftRequest(unittest.TestCase):
def setUp(self):
for doctype in ["Shift Request", "Shift Assignment"]:
@@ -46,4 +48,4 @@
department_doc = frappe.get_doc("Department", department)
department_doc.append('shift_request_approver',{'approver': "test1@example.com"})
department_doc.save()
- department_doc.reload()
\ No newline at end of file
+ department_doc.reload()
diff --git a/erpnext/hr/doctype/shift_type/test_records.json b/erpnext/hr/doctype/shift_type/test_records.json
new file mode 100644
index 0000000..9040b91
--- /dev/null
+++ b/erpnext/hr/doctype/shift_type/test_records.json
@@ -0,0 +1,8 @@
+[
+ {
+ "doctype": "Shift Type",
+ "name": "Day Shift",
+ "start_time": "9:00:00",
+ "end_time": "18:00:00"
+ }
+]
diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.py b/erpnext/hr/doctype/shift_type/test_shift_type.py
index 535072a..bc4f0ea 100644
--- a/erpnext/hr/doctype/shift_type/test_shift_type.py
+++ b/erpnext/hr/doctype/shift_type/test_shift_type.py
@@ -7,14 +7,4 @@
import unittest
class TestShiftType(unittest.TestCase):
- def test_make_shift_type(self):
- if frappe.db.exists("Shift Type", "Day Shift"):
- return
- shift_type = frappe.get_doc({
- "doctype": "Shift Type",
- "name": "Day Shift",
- "start_time": "9:00:00",
- "end_time": "18:00:00"
- })
- shift_type.insert()
-
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 5b84d00..533149a 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -39,6 +39,7 @@
detail.current_count = designation_counts['employee_count']
detail.current_openings = designation_counts['job_openings']
+ detail.total_estimated_cost = 0
if detail.number_of_positions > 0:
if detail.vacancies > 0 and detail.estimated_cost_per_position:
detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 662a06b..7aaf2a0 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -255,6 +255,9 @@
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = time_data[0].end_time if time_data else None
+ if data.get("workstation") != self.workstation:
+ # workstations can change in a job card
+ data.workstation = self.workstation
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 585a09d..cd9edee 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -333,8 +333,7 @@
"fieldname": "operations",
"fieldtype": "Table",
"label": "Operations",
- "options": "Work Order Operation",
- "read_only": 1
+ "options": "Work Order Operation"
},
{
"depends_on": "operations",
@@ -496,7 +495,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2020-05-05 19:32:43.323054",
+ "modified": "2021-03-16 13:27:51.116484",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
index 2ca9f16..fc27d35 100644
--- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
@@ -61,7 +61,7 @@
from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1)
self.period_list = get_period_list(from_date, self.filters.to_date,
- from_date, self.filters.to_date, None, self.filters.periodicity, ignore_fiscal_year=True)
+ from_date, self.filters.to_date, "Date Range", self.filters.periodicity, ignore_fiscal_year=True)
order_data = self.get_data_for_forecast() or []
diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py
index e947588..6a2a06d 100644
--- a/erpnext/non_profit/doctype/donation/donation.py
+++ b/erpnext/non_profit/doctype/donation/donation.py
@@ -91,6 +91,10 @@
if not data.event == 'payment.captured':
return
+ # to avoid capturing subscription payments as donations
+ if payment.description and 'subscription' in str(payment.description).lower():
+ return
+
donor = get_donor(payment.email)
if not donor:
donor = create_donor(payment)
@@ -119,7 +123,7 @@
'donor_name': donor.donor_name,
'email': donor.email,
'date': getdate(),
- 'amount': flt(payment.amount),
+ 'amount': flt(payment.amount) / 100, # Convert to rupees from paise
'mode_of_payment': payment.method,
'razorpay_payment_id': payment.id
}).insert(ignore_mandatory=True)
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index 191281f..52447e4 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -48,7 +48,7 @@
last_membership = erpnext.get_last_membership(self.member)
# if person applied for offline membership
- if last_membership and not frappe.session.user == "Administrator":
+ if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator":
# if last membership does not expire in 30 days, then do not allow to renew
if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
frappe.throw(_("You can only renew if your membership expires within 30 days"))
@@ -90,6 +90,7 @@
self.validate_membership_type_and_settings(plan, settings)
invoice = make_invoice(self, member, plan, settings)
+ self.reload()
self.invoice = invoice.name
if with_payment_entry:
@@ -284,10 +285,11 @@
settings = frappe.get_doc("Non Profit Settings")
if settings.allow_invoicing and settings.automate_membership_invoicing:
+ membership.reload()
membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
except Exception as e:
- message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
+ 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}
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7016ecd..3675184 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -760,3 +760,4 @@
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
+erpnext.patches.v13_0.rename_discharge_date_in_ip_record
diff --git a/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py
new file mode 100644
index 0000000..491dc82
--- /dev/null
+++ b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+ frappe.reload_doc("Healthcare", "doctype", "Inpatient Record")
+ if frappe.db.has_column("Inpatient Record", "discharge_date"):
+ rename_field("Inpatient Record", "discharge_date", "discharge_datetime")
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index f5af677..13b6c05 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -9,17 +9,10 @@
from frappe.utils import getdate, date_diff, comma_and, formatdate
class AdditionalSalary(Document):
-
def on_submit(self):
if self.ref_doctype == "Employee Advance" and self.ref_docname:
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
- def before_insert(self):
- if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
- "amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}):
-
- frappe.throw(_("Additional Salary Component Exists."))
-
def validate(self):
self.validate_dates()
self.validate_salary_structure()
@@ -89,10 +82,11 @@
no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
return amount_per_day * no_of_days
-@frappe.whitelist()
-def get_additional_salary_component(employee, start_date, end_date, component_type):
- additional_salaries = frappe.db.sql("""
- select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
+def get_additional_salaries(employee, start_date, end_date, component_type):
+ additional_salary_list = frappe.db.sql("""
+ select name, salary_component as component, type, amount,
+ overwrite_salary_structure_amount as overwrite,
+ deduct_full_tax_on_selected_payroll_date
from `tabAdditional Salary`
where employee=%(employee)s
and docstatus = 1
@@ -102,7 +96,7 @@
from_date <= %(to_date)s and to_date >= %(to_date)s
)
and type = %(component_type)s
- order by salary_component, overwrite_salary_structure_amount DESC
+ order by salary_component, overwrite ASC
""", {
'employee': employee,
'from_date': start_date,
@@ -110,38 +104,18 @@
'component_type': "Earning" if component_type == "earnings" else "Deduction"
}, as_dict=1)
- existing_salary_components= []
- salary_components_details = {}
- additional_salary_details = []
+ additional_salaries = []
+ components_to_overwrite = []
- overwrites_components = [ele.salary_component for ele in additional_salaries if ele.overwrite_salary_structure_amount == 1]
+ for d in additional_salary_list:
+ if d.overwrite:
+ if d.component in components_to_overwrite:
+ frappe.throw(_("Multiple Additional Salaries with overwrite "
+ "property exist for Salary Component {0} between {1} and {2}.").format(
+ frappe.bold(d.component), start_date, end_date), title=_("Error"))
- component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type']
- for d in additional_salaries:
+ components_to_overwrite.append(d.component)
- if d.salary_component not in existing_salary_components:
- component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields)
- struct_row = frappe._dict({'salary_component': d.salary_component})
- if component:
- struct_row.update(component[0])
+ additional_salaries.append(d)
- struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date
- struct_row['is_additional_component'] = 1
-
- salary_components_details[d.salary_component] = struct_row
-
-
- if overwrites_components.count(d.salary_component) > 1:
- frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component: {0} between {1} and {2}.".format(d.salary_component, start_date, end_date)), title=_("Error"))
- else:
- additional_salary_details.append({
- 'name': d.name,
- 'component': d.salary_component,
- 'amount': d.amount,
- 'type': d.type,
- 'overwrite': d.overwrite_salary_structure_amount,
- })
-
- existing_salary_components.append(d.salary_component)
-
- return salary_components_details, additional_salary_details
+ return additional_salaries
diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py
index e89e3dd..7daea2d 100644
--- a/erpnext/payroll/doctype/gratuity/test_gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py
@@ -15,9 +15,12 @@
test_dependencies = ["Salary Component", "Salary Slip", "Account"]
class TestGratuity(unittest.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
+
+ def setUp(self):
frappe.db.sql("DELETE FROM `tabGratuity`")
frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'")
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index e098ec7..84c3814 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -12,7 +12,7 @@
from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment
-from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
+from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
class TestPayrollEntry(unittest.TestCase):
@@ -168,15 +168,23 @@
salary_structure = "Test Salary Structure for Loan"
make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company", currency=company_doc.default_currency)
+ if not frappe.db.exists("Loan Type", "Car Loan"):
+ create_loan_accounts()
+ create_loan_type("Car Loan", 500000, 8.4,
+ is_term_loan=1,
+ mode_of_payment='Cash',
+ payment_account='Payment Account - _TC',
+ loan_account='Loan Account - _TC',
+ interest_income_account='Interest Income Account - _TC',
+ penalty_income_account='Penalty Income Account - _TC')
+
loan = create_loan(applicant, "Car Loan", 280000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
loan.repay_from_salary = 1
loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=add_months(nowdate(), -1))
-
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
-
dates = get_start_end_dates('Monthly', nowdate())
make_payroll_entry(company="_Test Company", start_date=dates.start_date, payable_account=company_doc.default_payroll_payable_account,
currency=company_doc.default_currency, end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")
@@ -267,4 +275,4 @@
salary_slip.calculate_net_pay()
salary_slip.db_update()
- return salary_slip
\ No newline at end of file
+ return salary_slip
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 595d697..a04a635 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -13,7 +13,7 @@
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.utilities.transaction_base import TransactionBase
from frappe.utils.background_jobs import enqueue
-from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salary_component
+from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries
from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
@@ -524,7 +524,7 @@
except NameError as err:
frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err),
- title=_("Name error"))
+ title=_("Name error"))
except SyntaxError as err:
frappe.throw(_("Syntax error in formula or condition: {0}").format(err))
except Exception as e:
@@ -558,15 +558,16 @@
self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
def add_additional_salary_components(self, component_type):
- salary_components_details, additional_salary_details = get_additional_salary_component(self.employee,
+ additional_salaries = get_additional_salaries(self.employee,
self.start_date, self.end_date, component_type)
- if salary_components_details and additional_salary_details:
- for additional_salary in additional_salary_details:
- additional_salary =frappe._dict(additional_salary)
- amount = additional_salary.amount
- overwrite = additional_salary.overwrite
- self.update_component_row(frappe._dict(salary_components_details[additional_salary.component]), amount,
- component_type, overwrite=overwrite, additional_salary=additional_salary.name)
+
+ for additional_salary in additional_salaries:
+ self.update_component_row(
+ get_salary_component_data(additional_salary.component),
+ additional_salary.amount,
+ component_type,
+ additional_salary
+ )
def add_tax_components(self, payroll_period):
# Calculate variable_based_on_taxable_salary after all components updated in salary slip
@@ -583,47 +584,59 @@
for d in tax_components:
tax_amount = self.calculate_variable_based_on_taxable_salary(d, payroll_period)
- tax_row = self.get_salary_slip_row(d)
+ tax_row = get_salary_component_data(d)
self.update_component_row(tax_row, tax_amount, "deductions")
- def update_component_row(self, struct_row, amount, key, overwrite=1, additional_salary = ''):
+ def update_component_row(self, component_data, amount, component_type, additional_salary=None):
component_row = None
- for d in self.get(key):
- if d.salary_component == struct_row.salary_component:
+ for d in self.get(component_type):
+ if d.salary_component != component_data.salary_component:
+ continue
+
+ if (
+ not d.additional_salary
+ and (not additional_salary or additional_salary.overwrite)
+ or additional_salary
+ and additional_salary.name == d.additional_salary
+ ):
component_row = d
+ break
- if not component_row or (struct_row.get("is_additional_component") and not overwrite):
- if amount:
- self.append(key, {
- 'amount': amount,
- 'default_amount': amount if not struct_row.get("is_additional_component") else 0,
- 'depends_on_payment_days' : struct_row.depends_on_payment_days,
- 'salary_component' : struct_row.salary_component,
- 'abbr' : struct_row.abbr or struct_row.get("salary_component_abbr"),
- 'additional_salary': additional_salary,
- 'do_not_include_in_total' : struct_row.do_not_include_in_total,
- 'is_tax_applicable': struct_row.is_tax_applicable,
- 'is_flexible_benefit': struct_row.is_flexible_benefit,
- 'variable_based_on_taxable_salary': struct_row.variable_based_on_taxable_salary,
- 'deduct_full_tax_on_selected_payroll_date': struct_row.deduct_full_tax_on_selected_payroll_date,
- 'additional_amount': amount if struct_row.get("is_additional_component") else 0,
- 'exempted_from_income_tax': struct_row.exempted_from_income_tax
- })
+ if additional_salary and additional_salary.overwrite:
+ # Additional Salary with overwrite checked, remove default rows of same component
+ self.set(component_type, [
+ d for d in self.get(component_type)
+ if d.salary_component != component_data.salary_component
+ or d.additional_salary and additional_salary.name != d.additional_salary
+ or d == component_row
+ ])
+
+ if not component_row:
+ if not amount:
+ return
+
+ component_row = self.append(component_type)
+ for attr in (
+ 'depends_on_payment_days', 'salary_component', 'abbr'
+ 'do_not_include_in_total', 'is_tax_applicable',
+ 'is_flexible_benefit', 'variable_based_on_taxable_salary',
+ 'exempted_from_income_tax'
+ ):
+ component_row.set(attr, component_data.get(attr))
+
+ if additional_salary:
+ component_row.default_amount = 0
+ component_row.additional_amount = amount
+ component_row.additional_salary = additional_salary.name
+ component_row.deduct_full_tax_on_selected_payroll_date = \
+ additional_salary.deduct_full_tax_on_selected_payroll_date
else:
- if struct_row.get("is_additional_component"):
- if overwrite:
- component_row.additional_amount = amount - component_row.get("default_amount", 0)
- component_row.additional_salary = additional_salary
- else:
- component_row.additional_amount = amount
+ component_row.default_amount = amount
+ component_row.additional_amount = 0
+ component_row.deduct_full_tax_on_selected_payroll_date = \
+ component_data.deduct_full_tax_on_selected_payroll_date
- if not overwrite and component_row.default_amount:
- amount += component_row.default_amount
- else:
- component_row.default_amount = amount
-
- component_row.amount = amount
- component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date
+ component_row.amount = amount
def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period):
if not payroll_period:
@@ -950,26 +963,13 @@
return frappe.safe_eval(condition, self.whitelisted_globals, data)
except NameError as err:
frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err),
- title=_("Name error"))
+ title=_("Name error"))
except SyntaxError as err:
frappe.throw(_("Syntax error in condition: {0}").format(err))
except Exception as e:
frappe.throw(_("Error in formula or condition: {0}").format(e))
raise
- def get_salary_slip_row(self, salary_component):
- component = frappe.get_doc("Salary Component", salary_component)
- # Data for update_component_row
- struct_row = frappe._dict()
- struct_row['depends_on_payment_days'] = component.depends_on_payment_days
- struct_row['salary_component'] = component.name
- struct_row['abbr'] = component.salary_component_abbr
- struct_row['do_not_include_in_total'] = component.do_not_include_in_total
- struct_row['is_tax_applicable'] = component.is_tax_applicable
- struct_row['is_flexible_benefit'] = component.is_flexible_benefit
- struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary
- return struct_row
-
def get_component_totals(self, component_type, depends_on_payment_days=0):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
@@ -1032,7 +1032,6 @@
self.total_loan_repayment += payment.total_payment
def get_loan_details(self):
-
return frappe.get_all("Loan",
fields=["name", "interest_income_account", "loan_account", "loan_type"],
filters = {
@@ -1263,3 +1262,19 @@
def generate_password_for_pdf(policy_template, employee):
employee = frappe.get_doc("Employee", employee)
return policy_template.format(**employee.as_dict())
+
+def get_salary_component_data(component):
+ return frappe.get_value(
+ "Salary Component",
+ component,
+ [
+ "name as salary_component",
+ "depends_on_payment_days",
+ "salary_component_abbr as abbr",
+ "do_not_include_in_total",
+ "is_tax_applicable",
+ "is_flexible_benefit",
+ "variable_based_on_taxable_salary",
+ ],
+ as_dict=1,
+ )
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 143a306..7672695 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -361,7 +361,6 @@
# as per assigned salary structure 40500 in monthly salary so 236000*5/100/12
frappe.db.sql("""delete from `tabPayroll Period`""")
frappe.db.sql("""delete from `tabSalary Component`""")
- frappe.db.sql("""delete from `tabAdditional Salary`""")
payroll_period = create_payroll_period()
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 8ba0b6c..f9e1359 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -81,12 +81,18 @@
def calculate_start_date(self, task_details):
self.start_date = add_days(self.expected_start_date, task_details.start)
- self.start_date = update_if_holiday(self.holiday_list, self.start_date)
+ self.start_date = self.update_if_holiday(self.start_date)
return self.start_date
def calculate_end_date(self, task_details):
self.end_date = add_days(self.start_date, task_details.duration)
- return update_if_holiday(self.holiday_list, self.end_date)
+ return self.update_if_holiday(self.end_date)
+
+ def update_if_holiday(self, date):
+ holiday_list = self.holiday_list or get_holiday_list(self.company)
+ while is_holiday(holiday_list, date):
+ date = add_days(date, 1)
+ return date
def dependency_mapping(self, template_tasks, project_tasks):
for template_task in template_tasks:
@@ -541,9 +547,3 @@
project.status = status
project.save()
-
-def update_if_holiday(holiday_list, date):
- holiday_list = holiday_list or get_holiday_list()
- while is_holiday(holiday_list, date):
- date = add_days(date, 1)
- return date
diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py
index 6290538..15a2873 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -8,7 +8,6 @@
test_ignore = ["Sales Order"]
from erpnext.projects.doctype.project_template.test_project_template import make_project_template
-from erpnext.projects.doctype.project.project import update_if_holiday
from erpnext.projects.doctype.task.test_task import create_task
from frappe.utils import getdate, nowdate, add_days
@@ -97,7 +96,8 @@
project_name = name,
status = 'Open',
project_template = template.name,
- expected_start_date = nowdate()
+ expected_start_date = nowdate(),
+ company="_Test Company"
)).insert()
return project
@@ -131,7 +131,7 @@
def calculate_end_date(project, start, duration):
start = add_days(project.expected_start_date, start)
- start = update_if_holiday(project.holiday_list, start)
+ start = project.update_if_holiday(start)
end = add_days(start, duration)
- end = update_if_holiday(project.holiday_list, end)
- return getdate(end)
\ No newline at end of file
+ end = project.update_if_holiday(end)
+ return getdate(end)
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 4cb3804..f7c764e 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -13,9 +13,18 @@
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.payroll.doctype.salary_structure.test_salary_structure \
import make_salary_structure, create_salary_structure_assignment
+from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
+ make_earning_salary_component,
+ make_deduction_salary_component
+)
from erpnext.hr.doctype.employee.test_employee import make_employee
class TestTimesheet(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ make_earning_salary_component(setup=True, company_list=['_Test Company'])
+ make_deduction_salary_component(setup=True, company_list=['_Test Company'])
+
def setUp(self):
for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]:
frappe.db.sql("delete from `tab%s`" % dt)
@@ -49,7 +58,7 @@
self.assertEqual(timesheet.total_billable_amount, 0)
def test_salary_slip_from_timesheet(self):
- emp = make_employee("test_employee_6@salary.com")
+ emp = make_employee("test_employee_6@salary.com", company="_Test Company")
salary_structure = make_salary_structure_for_timesheet(emp)
timesheet = make_timesheet(emp, simulate = True, billable=1)
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 1c0abdf..365851a 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -738,21 +738,15 @@
}
else {
var valid_serial_nos = [];
-
+ var serialnos = [];
// Replacing all occurences of comma with carriage return
- var serial_nos = item.serial_no.trim().replace(/,/g, '\n');
-
- serial_nos = serial_nos.trim().split('\n');
-
- // Trim each string and push unique string to new list
- for (var x=0; x<=serial_nos.length - 1; x++) {
- if (serial_nos[x].trim() != "" && valid_serial_nos.indexOf(serial_nos[x].trim()) == -1) {
- valid_serial_nos.push(serial_nos[x].trim());
+ item.serial_no = item.serial_no.replace(/,/g, '\n');
+ serialnos = item.serial_no.split("\n");
+ for (var i = 0; i < serialnos.length; i++) {
+ if (serialnos[i] != "") {
+ valid_serial_nos.push(serialnos[i]);
}
}
-
- // Add the new list to the serial no. field in grid with each in new line
- item.serial_no = valid_serial_nos.join('\n');
item.conversion_factor = item.conversion_factor || 1;
refresh_field("serial_no", item.name, item.parentfield);
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 68c8a0d..a49996d 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -349,13 +349,12 @@
return inter_state_supply_details
def get_inward_nil_exempt(self, state):
-
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 i.is_nil_exempt = 1 or i.is_non_gst = 1 and
+ 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 """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ 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_details = {
"gst": {
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 d734a18..5bbd575 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
@@ -16,6 +16,7 @@
self.validate_duplicates()
self.validate_company_details()
self.set_company_address()
+ self.calculate_total()
self.set_title()
def validate_date(self):
@@ -29,7 +30,10 @@
def validate_duplicates(self):
if self.recipient == 'Donor':
- certificate = frappe.db.exists(self.doctype, {'donation': self.donation})
+ certificate = frappe.db.exists(self.doctype, {
+ 'donation': self.donation,
+ 'name': ('!=', self.name)
+ })
if certificate:
frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format(
get_link_to_form(self.doctype, certificate), frappe.bold(self.donation)
@@ -51,8 +55,17 @@
self.company_address = address.company_address
self.company_address_display = address.company_address_display
+ def calculate_total(self):
+ if self.recipient == 'Donor':
+ return
+
+ total = 0
+ for entry in self.payments:
+ total += flt(entry.amount)
+ self.total = total
+
def set_title(self):
- if self.recipient == "Member":
+ if self.recipient == 'Member':
self.title = self.member_name
else:
self.title = self.donor_name
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index ee16f44..0fdfb1b 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1,11 +1,12 @@
# 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
import json
-from frappe.utils import flt, add_days, nowdate
-import frappe.permissions
import unittest
+import frappe
+import frappe.permissions
+from frappe.utils import flt, add_days, nowdate
+from frappe.core.doctype.user_permission.test_user_permission import create_user
from erpnext.selling.doctype.sales_order.sales_order \
import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -444,10 +445,8 @@
def test_update_child_perm(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
- user = 'test@example.com'
- test_user = frappe.get_doc('User', user)
- test_user.add_roles("Accounts User")
- frappe.set_user(user)
+ test_user = create_user("test_so_child_perms@example.com", "Accounts User")
+ frappe.set_user(test_user.name)
# update qty
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}])
@@ -456,18 +455,14 @@
# add new item
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
- test_user.remove_roles("Accounts User")
- frappe.set_user("Administrator")
def test_update_child_qty_rate_with_workflow(self):
from frappe.model.workflow import apply_workflow
- frappe.set_user("Administrator")
workflow = make_sales_order_workflow()
so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
apply_workflow(so, 'Approve')
- frappe.set_user("Administrator")
user = 'test@example.com'
test_user = frappe.get_doc('User', user)
test_user.add_roles("Sales User", "Test Junior Approver")
@@ -618,33 +613,31 @@
frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value)
def test_warehouse_user(self):
- frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
- frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
- frappe.permissions.add_user_permission("Company", "_Test Company 1", "test2@example.com")
-
- test_user = frappe.get_doc("User", "test@example.com")
- test_user.add_roles("Sales User", "Stock User")
- test_user.remove_roles("Sales Manager")
+ test_user = create_user("test_so_warehouse_user@example.com", "Sales User", "Stock User")
test_user_2 = frappe.get_doc("User", "test2@example.com")
test_user_2.add_roles("Sales User", "Stock User")
test_user_2.remove_roles("Sales Manager")
- frappe.set_user("test@example.com")
+ frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", test_user.name)
+ frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", test_user_2.name)
+ frappe.permissions.add_user_permission("Company", "_Test Company 1", test_user_2.name)
- so = make_sales_order(company="_Test Company 1",
+ frappe.set_user(test_user.name)
+
+ so = make_sales_order(company="_Test Company 1", customer="_Test Customer 1",
warehouse="_Test Warehouse 2 - _TC1", do_not_save=True)
so.conversion_rate = 0.02
so.plc_conversion_rate = 0.02
self.assertRaises(frappe.PermissionError, so.insert)
- frappe.set_user("test2@example.com")
+ frappe.set_user(test_user_2.name)
so.insert()
frappe.set_user("Administrator")
- frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
- frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
- frappe.permissions.remove_user_permission("Company", "_Test Company 1", "test2@example.com")
+ frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", test_user.name)
+ frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", test_user_2.name)
+ frappe.permissions.remove_user_permission("Company", "_Test Company 1", test_user_2.name)
def test_block_delivery_note_against_cancelled_sales_order(self):
so = make_sales_order()
diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py
index abff973..2ea0bc0 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.py
+++ b/erpnext/setup/doctype/naming_series/naming_series.py
@@ -10,6 +10,7 @@
from frappe.model.document import Document
from frappe.model.naming import parse_naming_series
from frappe.permissions import get_doctypes_with_read
+from frappe.core.doctype.doctype.doctype import validate_series
class NamingSeriesNotSetError(frappe.ValidationError): pass
@@ -126,7 +127,7 @@
dt = frappe.get_doc("DocType", self.select_doc_for_series)
options = self.scrub_options_list(self.set_options.split("\n"))
for series in options:
- dt.validate_series(series)
+ validate_series(dt, series)
for i in sr:
if i[0]:
existing_series = [d.split('.')[0] for d in i[0].split("\n")]
diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json
index 69ca7cf..305456b 100644
--- a/erpnext/setup/workspace/home/home.json
+++ b/erpnext/setup/workspace/home/home.json
@@ -10,13 +10,14 @@
"hide_custom": 0,
"icon": "getting-started",
"idx": 0,
+ "is_default": 0,
"is_standard": 1,
"label": "Home",
"links": [
{
"hidden": 0,
"is_query_report": 0,
- "label": "Healthcare",
+ "label": "Accounting",
"onboard": 0,
"type": "Card Break"
},
@@ -24,8 +25,8 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Patient",
- "link_to": "Patient",
+ "label": "Chart of Accounts",
+ "link_to": "Account",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
@@ -34,25 +35,8 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Diagnosis",
- "link_to": "Diagnosis",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Agriculture",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Crop",
- "link_to": "Crop",
+ "label": "Company",
+ "link_to": "Company",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
@@ -61,8 +45,8 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Crop Cycle",
- "link_to": "Crop Cycle",
+ "label": "Customer",
+ "link_to": "Customer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
@@ -71,112 +55,8 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Location",
- "link_to": "Location",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Fertilizer",
- "link_to": "Fertilizer",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Education",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Student",
- "link_to": "Student",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Course",
- "link_to": "Course",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Instructor",
- "link_to": "Instructor",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Room",
- "link_to": "Room",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Non Profit",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Member",
- "link_to": "Member",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Volunteer",
- "link_to": "Volunteer",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Chapter",
- "link_to": "Chapter",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Donor",
- "link_to": "Donor",
+ "label": "Supplier",
+ "link_to": "Supplier",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
@@ -192,6 +72,16 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
+ "label": "Item",
+ "link_to": "Item",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
"label": "Warehouse",
"link_to": "Warehouse",
"link_type": "DocType",
@@ -305,73 +195,6 @@
{
"hidden": 0,
"is_query_report": 0,
- "label": "Accounting",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Item",
- "link_to": "Item",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Customer",
- "link_to": "Customer",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Supplier",
- "link_to": "Supplier",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Company",
- "link_to": "Company",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Chart of Accounts",
- "link_to": "Account",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Opening Invoice Creation Tool",
- "link_to": "Opening Invoice Creation Tool",
- "link_type": "DocType",
- "onboard": 1,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
"label": "Data Import and Settings",
"onboard": 0,
"type": "Card Break"
@@ -390,6 +213,16 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
+ "label": "Opening Invoice Creation Tool",
+ "link_to": "Opening Invoice Creation Tool",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
"label": "Chart of Accounts Importer",
"link_to": "Chart of Accounts Importer",
"link_type": "DocType",
@@ -415,9 +248,177 @@
"link_type": "DocType",
"onboard": 1,
"type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Healthcare",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Patient",
+ "link_to": "Patient",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Diagnosis",
+ "link_to": "Diagnosis",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Education",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Student",
+ "link_to": "Student",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Instructor",
+ "link_to": "Instructor",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Course",
+ "link_to": "Course",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Room",
+ "link_to": "Room",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Non Profit",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Donor",
+ "link_to": "Donor",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Member",
+ "link_to": "Member",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Volunteer",
+ "link_to": "Volunteer",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Chapter",
+ "link_to": "Chapter",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Agriculture",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Location",
+ "link_to": "Location",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Crop",
+ "link_to": "Crop",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Crop Cycle",
+ "link_to": "Crop Cycle",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Fertilizer",
+ "link_to": "Fertilizer",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
}
],
- "modified": "2021-01-01 12:13:16.055668",
+ "modified": "2021-03-16 15:59:58.416154",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 0da57b7..d723fac 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -25,8 +25,8 @@
if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
continue
if not item.serial_no:
- frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}".format(
- frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse))),
+ frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}").format(
+ frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse)),
title=_("Serial Nos Required"))
if len(item.serial_no.split('\n')) == item.picked_qty:
continue
@@ -380,7 +380,7 @@
stock_entry.set_incoming_rate()
stock_entry.set_actual_qty()
- stock_entry.calculate_rate_and_amount(update_finished_item_rate=False)
+ stock_entry.calculate_rate_and_amount()
return stock_entry.as_dict()
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index f0a90f9..b452e96 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -29,6 +29,8 @@
self.remove_items_with_no_change()
self.validate_data()
self.validate_expense_account()
+ self.validate_customer_provided_item()
+ self.set_zero_value_for_customer_provided_items()
self.set_total_qty_and_amount()
self.validate_putaway_capacity()
@@ -217,7 +219,7 @@
if row.valuation_rate in ("", None):
row.valuation_rate = previous_sle.get("valuation_rate", 0)
- if row.qty and not row.valuation_rate:
+ if row.qty and not row.valuation_rate and not row.allow_zero_valuation_rate:
frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
@@ -436,6 +438,20 @@
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError)
+ def set_zero_value_for_customer_provided_items(self):
+ changed_any_values = False
+
+ for d in self.get('items'):
+ is_customer_item = frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item')
+ if is_customer_item and d.valuation_rate:
+ d.valuation_rate = 0.0
+ changed_any_values = True
+
+ if changed_any_values:
+ msgprint(_("Valuation rate for customer provided items has been set to zero."),
+ title=_("Note"), indicator="blue")
+
+
def set_total_qty_and_amount(self):
for d in self.get("items"):
d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate"))
@@ -531,4 +547,4 @@
account = frappe.db.get_value('Account', {'is_group': 0,
'company': company, 'account_type': 'Temporary'}, 'name')
- return account
\ No newline at end of file
+ return account
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 088456f..6690c6a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -193,6 +193,16 @@
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
+ def test_customer_provided_items(self):
+ item_code = 'Stock-Reco-customer-Item-100'
+ create_item(item_code, is_customer_provided_item = 1,
+ customer = '_Test Customer', is_purchase_item = 0)
+
+ sr = create_stock_reconciliation(item_code = item_code, qty = 10, rate = 420)
+
+ self.assertEqual(sr.get("items")[0].allow_zero_valuation_rate, 1)
+ self.assertEqual(sr.get("items")[0].valuation_rate, 0)
+ self.assertEqual(sr.get("items")[0].amount, 0)
def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
index e53db07..85c7ebe 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -13,6 +13,7 @@
"qty",
"valuation_rate",
"amount",
+ "allow_zero_valuation_rate",
"serial_no_and_batch_section",
"serial_no",
"column_break_11",
@@ -166,10 +167,19 @@
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_zero_valuation_rate",
+ "fieldtype": "Check",
+ "label": "Allow Zero Valuation Rate",
+ "print_hide": 1,
+ "read_only": 1
}
],
"istable": 1,
- "modified": "2019-06-14 17:10:53.188305",
+ "links": [],
+ "modified": "2021-03-23 11:09:44.407157",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
@@ -179,4 +189,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index e5d4d62..6dfede4 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -198,7 +198,7 @@
else:
qty_diff = flt(d.actual_qty)
- value_diff = flt(d.stock_value) - flt(qty_dict.bal_val)
+ value_diff = flt(d.stock_value_difference)
if d.posting_date < from_date:
qty_dict.opening_qty += qty_diff
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 483bb15..46d02d8 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -12,7 +12,6 @@
class TestIssue(unittest.TestCase):
def setUp(self):
frappe.db.sql("delete from `tabService Level Agreement`")
- frappe.db.sql("delete from `tabEmployee`")
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
create_service_level_agreements_for_issues()
diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py
index 3d73531..7861e30 100644
--- a/erpnext/support/report/issue_summary/issue_summary.py
+++ b/erpnext/support/report/issue_summary/issue_summary.py
@@ -260,8 +260,7 @@
self.issue_summary_data[value]['avg_user_resolution_time'] = entry.get('avg_user_resolution_time') or 0.0
def get_chart_data(self):
- if not self.data:
- return None
+ self.chart = []
labels = []
open_issues = []
@@ -310,8 +309,7 @@
}
def get_report_summary(self):
- if not self.data:
- return None
+ self.report_summary = []
open_issues = 0
replied = 0
diff --git a/sider.yml b/sider.yml
new file mode 100644
index 0000000..2ca6e8d
--- /dev/null
+++ b/sider.yml
@@ -0,0 +1,3 @@
+linter:
+ flake8:
+ config: .flake8
\ No newline at end of file